omegon 0.6.4 → 0.6.6
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/bin/omegon.mjs +48 -8
- package/extensions/bootstrap/index.ts +1 -0
- package/extensions/cleave/dispatcher.ts +7 -5
- package/extensions/cleave/index.ts +7 -4
- package/extensions/lib/omegon-subprocess.ts +29 -0
- package/extensions/project-memory/extraction-v2.ts +4 -1
- package/extensions/project-memory/factstore.ts +59 -26
- package/package.json +1 -1
package/bin/omegon.mjs
CHANGED
|
@@ -2,39 +2,79 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Omegon entry point.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* from omegon rather than from ~/.pi/agent/.
|
|
5
|
+
* Keeps mutable user state in the shared pi-compatible agent directory while
|
|
6
|
+
* injecting Omegon-packaged resources from the installed package root.
|
|
8
7
|
*
|
|
9
8
|
* Resolution order for the underlying agent core:
|
|
10
9
|
* 1. vendor/pi-mono (dev mode — git submodule present)
|
|
11
10
|
* 2. node_modules/@styrene-lab/pi-coding-agent (installed via npm)
|
|
12
11
|
*/
|
|
12
|
+
import { copyFileSync, cpSync, existsSync, mkdirSync } from "node:fs";
|
|
13
13
|
import { dirname, join } from "node:path";
|
|
14
|
-
import {
|
|
14
|
+
import { homedir } from "node:os";
|
|
15
15
|
import { fileURLToPath } from "node:url";
|
|
16
16
|
|
|
17
17
|
const __filename = fileURLToPath(import.meta.url);
|
|
18
18
|
const omegonRoot = dirname(dirname(__filename));
|
|
19
|
+
const defaultStateDir = join(homedir(), ".pi", "agent");
|
|
20
|
+
const stateDir = process.env.PI_CODING_AGENT_DIR || defaultStateDir;
|
|
21
|
+
const usingExplicitStateOverride = Boolean(process.env.PI_CODING_AGENT_DIR);
|
|
19
22
|
|
|
20
23
|
const vendorCli = join(omegonRoot, "vendor/pi-mono/packages/coding-agent/dist/cli.js");
|
|
21
24
|
const npmCli = join(omegonRoot, "node_modules/@styrene-lab/pi-coding-agent/dist/cli.js");
|
|
22
25
|
const cli = existsSync(vendorCli) ? vendorCli : npmCli;
|
|
23
26
|
const resolutionMode = cli === vendorCli ? "vendor" : "npm";
|
|
24
27
|
|
|
28
|
+
function migrateLegacyStatePath(relativePath, kind = "file") {
|
|
29
|
+
if (usingExplicitStateOverride) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const legacyPath = join(omegonRoot, relativePath);
|
|
34
|
+
const targetPath = join(stateDir, relativePath);
|
|
35
|
+
if (!existsSync(legacyPath) || existsSync(targetPath)) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
mkdirSync(dirname(targetPath), { recursive: true });
|
|
40
|
+
if (kind === "directory") {
|
|
41
|
+
cpSync(legacyPath, targetPath, { recursive: true, force: false });
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
copyFileSync(legacyPath, targetPath);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function injectBundledResourceArgs(argv) {
|
|
48
|
+
const injected = [...argv];
|
|
49
|
+
const pushPair = (flag, value) => {
|
|
50
|
+
if (existsSync(value)) {
|
|
51
|
+
injected.push(flag, value);
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
pushPair("--extension", omegonRoot);
|
|
56
|
+
pushPair("--skill", join(omegonRoot, "skills"));
|
|
57
|
+
pushPair("--prompt-template", join(omegonRoot, "prompts"));
|
|
58
|
+
pushPair("--theme", join(omegonRoot, "themes"));
|
|
59
|
+
return injected;
|
|
60
|
+
}
|
|
61
|
+
|
|
25
62
|
if (process.argv.includes("--where")) {
|
|
26
63
|
process.stdout.write(JSON.stringify({
|
|
27
64
|
omegonRoot,
|
|
28
65
|
cli,
|
|
29
66
|
resolutionMode,
|
|
30
|
-
agentDir:
|
|
67
|
+
agentDir: stateDir,
|
|
68
|
+
stateDir,
|
|
31
69
|
executable: "omegon",
|
|
32
70
|
}, null, 2) + "\n");
|
|
33
71
|
process.exit(0);
|
|
34
72
|
}
|
|
35
73
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
74
|
+
process.env.PI_CODING_AGENT_DIR = stateDir;
|
|
75
|
+
migrateLegacyStatePath("auth.json");
|
|
76
|
+
migrateLegacyStatePath("settings.json");
|
|
77
|
+
migrateLegacyStatePath("sessions", "directory");
|
|
78
|
+
process.argv = injectBundledResourceArgs(process.argv);
|
|
39
79
|
|
|
40
80
|
await import(cli);
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* cleave/dispatcher — Child process dispatch and monitoring.
|
|
3
3
|
*
|
|
4
|
-
* Spawns
|
|
4
|
+
* Spawns Omegon-owned subprocesses for each child task, using the same
|
|
5
5
|
* subagent pattern as pi's example extension. Each child runs in
|
|
6
6
|
* its own git worktree with an isolated context.
|
|
7
7
|
*
|
|
8
8
|
* Supports two backends:
|
|
9
|
-
* - "cloud": spawns a full
|
|
10
|
-
* - "local": spawns
|
|
9
|
+
* - "cloud": spawns a full Omegon child process (uses cloud API)
|
|
10
|
+
* - "local": spawns Omegon with --model pointing to a local Ollama model
|
|
11
11
|
*
|
|
12
12
|
* The dispatcher handles:
|
|
13
13
|
* - Dependency-ordered wave execution
|
|
@@ -26,6 +26,7 @@ import { computeDispatchWaves } from "./planner.ts";
|
|
|
26
26
|
import { executeWithReview, type ReviewConfig, type ReviewExecutor, DEFAULT_REVIEW_CONFIG } from "./review.ts";
|
|
27
27
|
import { saveState } from "./workspace.ts";
|
|
28
28
|
import { resolveTier, getDefaultPolicy, getViableModels, type ProviderRoutingPolicy, type RegistryModel } from "../lib/model-routing.ts";
|
|
29
|
+
import { resolveOmegonSubprocess } from "../lib/omegon-subprocess.ts";
|
|
29
30
|
|
|
30
31
|
// ─── Large-run threshold ────────────────────────────────────────────────────
|
|
31
32
|
|
|
@@ -370,7 +371,8 @@ async function spawnChild(
|
|
|
370
371
|
localModel?: string,
|
|
371
372
|
onLine?: (line: string) => void,
|
|
372
373
|
): Promise<ChildResult> {
|
|
373
|
-
const
|
|
374
|
+
const omegon = resolveOmegonSubprocess();
|
|
375
|
+
const args = [...omegon.argvPrefix, "-p", "--no-session"];
|
|
374
376
|
if (localModel) {
|
|
375
377
|
args.push("--model", localModel);
|
|
376
378
|
}
|
|
@@ -380,7 +382,7 @@ async function spawnChild(
|
|
|
380
382
|
let stderr = "";
|
|
381
383
|
let killed = false;
|
|
382
384
|
|
|
383
|
-
const proc = spawn(
|
|
385
|
+
const proc = spawn(omegon.command, args, {
|
|
384
386
|
cwd,
|
|
385
387
|
stdio: ["pipe", "pipe", "pipe"],
|
|
386
388
|
env: {
|
|
@@ -29,6 +29,7 @@ import { debug } from "../lib/debug.ts";
|
|
|
29
29
|
import { emitOpenSpecState } from "../openspec/dashboard-state.ts";
|
|
30
30
|
import { getSharedBridge, buildSlashCommandResult } from "../lib/slash-command-bridge.ts";
|
|
31
31
|
import { buildAssessBridgeResult } from "./bridge.ts";
|
|
32
|
+
import { resolveOmegonSubprocess } from "../lib/omegon-subprocess.ts";
|
|
32
33
|
import {
|
|
33
34
|
assessDirective,
|
|
34
35
|
PATTERNS,
|
|
@@ -755,11 +756,12 @@ async function runSpecAssessmentSubprocess(
|
|
|
755
756
|
...(input.diffContent ? ["### Recent Changes", "", "```diff", input.diffContent, "```", ""] : []),
|
|
756
757
|
].join("\n");
|
|
757
758
|
|
|
758
|
-
const
|
|
759
|
+
const omegon = resolveOmegonSubprocess();
|
|
760
|
+
const args = [...omegon.argvPrefix, "--mode", "json", "--plan", "-p", "--no-session"];
|
|
759
761
|
if (input.modelId) args.push("--model", input.modelId);
|
|
760
762
|
|
|
761
763
|
return await new Promise<SpecAssessmentRunnerOutput>((resolve, reject) => {
|
|
762
|
-
const proc = spawn(
|
|
764
|
+
const proc = spawn(omegon.command, args, {
|
|
763
765
|
cwd: input.repoPath,
|
|
764
766
|
shell: false,
|
|
765
767
|
stdio: ["pipe", "pipe", "pipe"],
|
|
@@ -1454,12 +1456,13 @@ async function runDesignAssessmentSubprocess(
|
|
|
1454
1456
|
"}",
|
|
1455
1457
|
].join("\n");
|
|
1456
1458
|
|
|
1457
|
-
const
|
|
1459
|
+
const omegon = resolveOmegonSubprocess();
|
|
1460
|
+
const args = [...omegon.argvPrefix, "--mode", "json", "--plan", "-p", "--no-session"];
|
|
1458
1461
|
if (modelId) args.push("--model", modelId);
|
|
1459
1462
|
|
|
1460
1463
|
return await new Promise<{ findings: DesignAssessmentFinding[]; nodeTitle: string; structuralPass: boolean }>(
|
|
1461
1464
|
(resolve, reject) => {
|
|
1462
|
-
const proc = spawn(
|
|
1465
|
+
const proc = spawn(omegon.command, args, {
|
|
1463
1466
|
cwd: repoPath,
|
|
1464
1467
|
shell: false,
|
|
1465
1468
|
stdio: ["pipe", "pipe", "pipe"],
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { fileURLToPath } from "node:url";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
|
|
4
|
+
export interface OmegonSubprocessSpec {
|
|
5
|
+
command: string;
|
|
6
|
+
argvPrefix: string[];
|
|
7
|
+
omegonEntry: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
let cached: OmegonSubprocessSpec | null = null;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Resolve the canonical Omegon-owned subprocess entrypoint without relying on PATH.
|
|
14
|
+
*
|
|
15
|
+
* Internal helpers should spawn `process.execPath` with `bin/omegon.mjs` explicitly,
|
|
16
|
+
* rather than assuming a `pi` or `omegon` binary on PATH points back to this install.
|
|
17
|
+
*/
|
|
18
|
+
export function resolveOmegonSubprocess(): OmegonSubprocessSpec {
|
|
19
|
+
if (cached) return cached;
|
|
20
|
+
|
|
21
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
22
|
+
const omegonEntry = join(here, "..", "..", "bin", "omegon.mjs");
|
|
23
|
+
cached = {
|
|
24
|
+
command: process.execPath,
|
|
25
|
+
argvPrefix: [omegonEntry],
|
|
26
|
+
omegonEntry,
|
|
27
|
+
};
|
|
28
|
+
return cached;
|
|
29
|
+
}
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
import { spawn, type ChildProcess } from "node:child_process";
|
|
16
16
|
import type { MemoryConfig } from "./types.ts";
|
|
17
17
|
import type { Fact, Edge } from "./factstore.ts";
|
|
18
|
+
import { resolveOmegonSubprocess } from "../lib/omegon-subprocess.ts";
|
|
18
19
|
|
|
19
20
|
// ---------------------------------------------------------------------------
|
|
20
21
|
// Shared subprocess runner
|
|
@@ -95,7 +96,9 @@ function spawnExtraction(opts: {
|
|
|
95
96
|
return;
|
|
96
97
|
}
|
|
97
98
|
|
|
99
|
+
const omegon = resolveOmegonSubprocess();
|
|
98
100
|
const args = [
|
|
101
|
+
...omegon.argvPrefix,
|
|
99
102
|
"--model", opts.model,
|
|
100
103
|
"--no-session", "--no-tools", "--no-extensions",
|
|
101
104
|
"--no-skills", "--no-themes", "--thinking", "off",
|
|
@@ -103,7 +106,7 @@ function spawnExtraction(opts: {
|
|
|
103
106
|
"-p", opts.userMessage,
|
|
104
107
|
];
|
|
105
108
|
|
|
106
|
-
const proc = spawn(
|
|
109
|
+
const proc = spawn(omegon.command, args, {
|
|
107
110
|
cwd: opts.cwd,
|
|
108
111
|
stdio: ["ignore", "pipe", "pipe"],
|
|
109
112
|
// Detach into new session so child has no controlling terminal.
|
|
@@ -91,6 +91,30 @@ function loadDatabase(): any {
|
|
|
91
91
|
|
|
92
92
|
const Database = loadDatabase();
|
|
93
93
|
|
|
94
|
+
function buildSafeFtsQuery(query: string, joiner: "AND" | "OR" = "AND"): string {
|
|
95
|
+
const tokens = query
|
|
96
|
+
.split(/\s+/)
|
|
97
|
+
.map(token => token.trim())
|
|
98
|
+
.filter(token => token.length > 0)
|
|
99
|
+
.map(token => token.replace(/"/g, '""'))
|
|
100
|
+
.filter(token => /[\p{L}\p{N}]/u.test(token))
|
|
101
|
+
.map(token => `"${token}"`);
|
|
102
|
+
|
|
103
|
+
return tokens.join(` ${joiner} `);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function isIgnorableFtsQueryError(error: unknown): boolean {
|
|
107
|
+
if (!(error instanceof Error)) return false;
|
|
108
|
+
const message = error.message.toLowerCase();
|
|
109
|
+
return (
|
|
110
|
+
message.includes("fts5")
|
|
111
|
+
|| message.includes("malformed match expression")
|
|
112
|
+
|| message.includes("syntax error")
|
|
113
|
+
|| message.includes("unterminated string")
|
|
114
|
+
|| message.includes("no such column")
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
94
118
|
/** Generate a short unique ID */
|
|
95
119
|
function nanoid(size = 12): string {
|
|
96
120
|
const bytes = crypto.randomBytes(size);
|
|
@@ -234,6 +258,7 @@ export class FactStore {
|
|
|
234
258
|
fs.mkdirSync(memoryDir, { recursive: true });
|
|
235
259
|
this.db = new Database(this.dbPath);
|
|
236
260
|
this.db.pragma("journal_mode = WAL");
|
|
261
|
+
this.db.pragma("busy_timeout = 5000");
|
|
237
262
|
this.db.pragma("foreign_keys = ON");
|
|
238
263
|
this.initSchema();
|
|
239
264
|
this.runMigrations();
|
|
@@ -1027,47 +1052,56 @@ export class FactStore {
|
|
|
1027
1052
|
|
|
1028
1053
|
/** Full-text search across all facts (all minds, all statuses) */
|
|
1029
1054
|
searchFacts(query: string, mind?: string): Fact[] {
|
|
1030
|
-
|
|
1031
|
-
const ftsQuery = query.split(/\s+/).filter(t => t.length > 0).join(" AND ");
|
|
1055
|
+
const ftsQuery = buildSafeFtsQuery(query, "AND");
|
|
1032
1056
|
if (!ftsQuery) return [];
|
|
1033
1057
|
|
|
1034
|
-
|
|
1058
|
+
try {
|
|
1059
|
+
if (mind) {
|
|
1060
|
+
return this.db.prepare(`
|
|
1061
|
+
SELECT f.* FROM facts f
|
|
1062
|
+
JOIN facts_fts fts ON f.rowid = fts.rowid
|
|
1063
|
+
WHERE facts_fts MATCH ? AND f.mind = ?
|
|
1064
|
+
ORDER BY rank
|
|
1065
|
+
`).all(ftsQuery, mind) as Fact[];
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1035
1068
|
return this.db.prepare(`
|
|
1036
1069
|
SELECT f.* FROM facts f
|
|
1037
1070
|
JOIN facts_fts fts ON f.rowid = fts.rowid
|
|
1038
|
-
WHERE facts_fts MATCH ?
|
|
1071
|
+
WHERE facts_fts MATCH ?
|
|
1039
1072
|
ORDER BY rank
|
|
1040
|
-
`).all(ftsQuery
|
|
1073
|
+
`).all(ftsQuery) as Fact[];
|
|
1074
|
+
} catch (error) {
|
|
1075
|
+
if (isIgnorableFtsQueryError(error)) return [];
|
|
1076
|
+
throw error;
|
|
1041
1077
|
}
|
|
1042
|
-
|
|
1043
|
-
return this.db.prepare(`
|
|
1044
|
-
SELECT f.* FROM facts f
|
|
1045
|
-
JOIN facts_fts fts ON f.rowid = fts.rowid
|
|
1046
|
-
WHERE facts_fts MATCH ?
|
|
1047
|
-
ORDER BY rank
|
|
1048
|
-
`).all(ftsQuery) as Fact[];
|
|
1049
1078
|
}
|
|
1050
1079
|
|
|
1051
1080
|
/** Search archived/superseded facts (replaces searchArchive) */
|
|
1052
1081
|
searchArchive(query: string, mind?: string): Fact[] {
|
|
1053
|
-
const ftsQuery = query
|
|
1082
|
+
const ftsQuery = buildSafeFtsQuery(query, "AND");
|
|
1054
1083
|
if (!ftsQuery) return [];
|
|
1055
1084
|
|
|
1056
|
-
|
|
1085
|
+
try {
|
|
1086
|
+
if (mind) {
|
|
1087
|
+
return this.db.prepare(`
|
|
1088
|
+
SELECT f.* FROM facts f
|
|
1089
|
+
JOIN facts_fts fts ON f.rowid = fts.rowid
|
|
1090
|
+
WHERE facts_fts MATCH ? AND f.mind = ? AND f.status IN ('archived', 'superseded')
|
|
1091
|
+
ORDER BY f.created_at DESC
|
|
1092
|
+
`).all(ftsQuery, mind) as Fact[];
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1057
1095
|
return this.db.prepare(`
|
|
1058
1096
|
SELECT f.* FROM facts f
|
|
1059
1097
|
JOIN facts_fts fts ON f.rowid = fts.rowid
|
|
1060
|
-
WHERE facts_fts MATCH ? AND f.
|
|
1098
|
+
WHERE facts_fts MATCH ? AND f.status IN ('archived', 'superseded')
|
|
1061
1099
|
ORDER BY f.created_at DESC
|
|
1062
|
-
`).all(ftsQuery
|
|
1100
|
+
`).all(ftsQuery) as Fact[];
|
|
1101
|
+
} catch (error) {
|
|
1102
|
+
if (isIgnorableFtsQueryError(error)) return [];
|
|
1103
|
+
throw error;
|
|
1063
1104
|
}
|
|
1064
|
-
|
|
1065
|
-
return this.db.prepare(`
|
|
1066
|
-
SELECT f.* FROM facts f
|
|
1067
|
-
JOIN facts_fts fts ON f.rowid = fts.rowid
|
|
1068
|
-
WHERE facts_fts MATCH ? AND f.status IN ('archived', 'superseded')
|
|
1069
|
-
ORDER BY f.created_at DESC
|
|
1070
|
-
`).all(ftsQuery) as Fact[];
|
|
1071
1105
|
}
|
|
1072
1106
|
|
|
1073
1107
|
/** Get a single fact by ID */
|
|
@@ -1850,9 +1884,8 @@ export class FactStore {
|
|
|
1850
1884
|
const ftsRanked: Map<string, number> = new Map(); // fact.id → rank (0-indexed)
|
|
1851
1885
|
if (queryText.length > 2) {
|
|
1852
1886
|
// Use OR mode for broader recall — AND is too restrictive for injection
|
|
1853
|
-
const
|
|
1854
|
-
if (
|
|
1855
|
-
const ftsQuery = tokens.join(" OR ");
|
|
1887
|
+
const ftsQuery = buildSafeFtsQuery(queryText, "OR");
|
|
1888
|
+
if (ftsQuery) {
|
|
1856
1889
|
try {
|
|
1857
1890
|
let query = `
|
|
1858
1891
|
SELECT f.* FROM facts f
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "omegon",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.6",
|
|
4
4
|
"description": "Omegon — an opinionated distribution of pi (by Mario Zechner) with extensions for lifecycle management, memory, orchestration, and visualization",
|
|
5
5
|
"bin": {
|
|
6
6
|
"omegon": "bin/omegon.mjs",
|