omegon 0.6.5 → 0.6.7

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/README.md CHANGED
@@ -10,6 +10,8 @@ An opinionated distribution of [**pi**](https://github.com/badlogic/pi) — the
10
10
  npm install -g omegon
11
11
  ```
12
12
 
13
+ **Node requirement:** Omegon requires **Node.js 20+**. Node 18 will fail because bundled pi-tui uses modern Unicode regex features unsupported by older runtimes.
14
+
13
15
  This installs the canonical `omegon` command globally. A legacy `pi` alias may remain available for compatibility, but the supported lifecycle entrypoint is `omegon`. If a standalone pi package is already installed, omegon transparently takes ownership of the lifecycle boundary so startup, update, verification, and restart all stay inside Omegon control. To switch back to standalone pi at any time:
14
16
 
15
17
  ```bash
@@ -1,13 +1,13 @@
1
1
  /**
2
2
  * cleave/dispatcher — Child process dispatch and monitoring.
3
3
  *
4
- * Spawns `pi` subprocesses for each child task, using the same
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 `pi` process (uses cloud API)
10
- * - "local": spawns `pi` with --model pointing to a local Ollama model
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 args = ["-p", "--no-session"];
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("pi", args, {
385
+ const proc = spawn(omegon.command, args, {
384
386
  cwd,
385
387
  stdio: ["pipe", "pipe", "pipe"],
386
388
  env: {
@@ -20,6 +20,8 @@ import { truncateTail, DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES, formatSize } from "
20
20
  import { Text } from "@styrene-lab/pi-tui";
21
21
  import { Type } from "@sinclair/typebox";
22
22
  import { spawn, execFile } from "node:child_process";
23
+ import * as fs from "node:fs";
24
+ import * as path from "node:path";
23
25
  import { promisify } from "node:util";
24
26
  import { createHash } from "node:crypto";
25
27
 
@@ -29,12 +31,12 @@ import { debug } from "../lib/debug.ts";
29
31
  import { emitOpenSpecState } from "../openspec/dashboard-state.ts";
30
32
  import { getSharedBridge, buildSlashCommandResult } from "../lib/slash-command-bridge.ts";
31
33
  import { buildAssessBridgeResult } from "./bridge.ts";
34
+ import { resolveOmegonSubprocess } from "../lib/omegon-subprocess.ts";
35
+ import { scanDesignDocs, getNodeSections } from "../design-tree/tree.ts";
32
36
  import {
33
37
  assessDirective,
34
38
  PATTERNS,
35
39
  runDesignStructuralCheck,
36
- buildDesignAssessmentPrompt,
37
- parseDesignAssessmentFindings,
38
40
  type AssessCompletion,
39
41
  type AssessEffect,
40
42
  type AssessLifecycleHint,
@@ -755,11 +757,12 @@ async function runSpecAssessmentSubprocess(
755
757
  ...(input.diffContent ? ["### Recent Changes", "", "```diff", input.diffContent, "```", ""] : []),
756
758
  ].join("\n");
757
759
 
758
- const args = ["--mode", "json", "--plan", "-p", "--no-session"];
760
+ const omegon = resolveOmegonSubprocess();
761
+ const args = [...omegon.argvPrefix, "--mode", "json", "--plan", "-p", "--no-session"];
759
762
  if (input.modelId) args.push("--model", input.modelId);
760
763
 
761
764
  return await new Promise<SpecAssessmentRunnerOutput>((resolve, reject) => {
762
- const proc = spawn("pi", args, {
765
+ const proc = spawn(omegon.command, args, {
763
766
  cwd: input.repoPath,
764
767
  shell: false,
765
768
  stdio: ["pipe", "pipe", "pipe"],
@@ -1420,118 +1423,6 @@ async function executeAssessComplexity(args: string): Promise<AssessStructuredRe
1420
1423
  });
1421
1424
  }
1422
1425
 
1423
- async function runDesignAssessmentSubprocess(
1424
- repoPath: string,
1425
- nodeId: string,
1426
- modelId?: string,
1427
- ): Promise<{ findings: DesignAssessmentFinding[]; nodeTitle: string; structuralPass: boolean }> {
1428
- const prompt = [
1429
- "You are performing a read-only design-tree node assessment.",
1430
- "Operate in read-only plan mode. Never call edit, write, or any workspace-mutating command.",
1431
- "",
1432
- `## Task`,
1433
- "",
1434
- `1. Call design_tree with action='node', node_id='${nodeId}' to load the node.`,
1435
- "2. Run the structural pre-check:",
1436
- " - open_questions must be empty — if not, emit a structural finding for each",
1437
- " - decisions must have at least one entry — if not, emit a structural finding",
1438
- " - acceptanceCriteria must have at least one scenario, falsifiability, or constraint — if not, emit a structural finding",
1439
- "3. If structural pre-check fails, output ONLY the JSON result below and stop.",
1440
- "4. Otherwise, evaluate each acceptance criterion against the document body:",
1441
- " - For each Scenario (Given/When/Then): does the document body address the Then clause?",
1442
- " - For each Falsifiability condition: is it addressed, ruled out, or acknowledged as a known risk?",
1443
- " - For each Constraint: is it satisfied by the document content?",
1444
- "",
1445
- "## Output Format",
1446
- "",
1447
- "Output ONLY a single JSON object (no prose, no markdown, no code blocks):",
1448
- "{",
1449
- ' "nodeTitle": "<title from node>",',
1450
- ' "structuralPass": true|false,',
1451
- ' "findings": [',
1452
- ' {"type":"scenario"|"falsifiability"|"constraint"|"structural","index":N,"pass":true|false,"finding":"<reason>"}',
1453
- " ]",
1454
- "}",
1455
- ].join("\n");
1456
-
1457
- const args = ["--mode", "json", "--plan", "-p", "--no-session"];
1458
- if (modelId) args.push("--model", modelId);
1459
-
1460
- return await new Promise<{ findings: DesignAssessmentFinding[]; nodeTitle: string; structuralPass: boolean }>(
1461
- (resolve, reject) => {
1462
- const proc = spawn("pi", args, {
1463
- cwd: repoPath,
1464
- shell: false,
1465
- stdio: ["pipe", "pipe", "pipe"],
1466
- env: { ...process.env, PI_CHILD: "1", TERM: process.env.TERM ?? "dumb" },
1467
- });
1468
- let buffer = "";
1469
- let assistantText = "";
1470
- let settled = false;
1471
- const settleReject = (error: Error) => {
1472
- if (settled) return;
1473
- settled = true;
1474
- clearTimeout(timer);
1475
- reject(error);
1476
- };
1477
- const settleResolve = (value: { findings: DesignAssessmentFinding[]; nodeTitle: string; structuralPass: boolean }) => {
1478
- if (settled) return;
1479
- settled = true;
1480
- clearTimeout(timer);
1481
- resolve(value);
1482
- };
1483
- const timer = setTimeout(() => {
1484
- proc.kill("SIGTERM");
1485
- setTimeout(() => { if (!proc.killed) proc.kill("SIGKILL"); }, 5_000);
1486
- settleReject(new Error(`Timed out after 120s while assessing design node ${nodeId}.`));
1487
- }, 120_000);
1488
- const processLine = (line: string) => {
1489
- if (!line.trim()) return;
1490
- let event: unknown;
1491
- try { event = JSON.parse(line); } catch { return; }
1492
- if (!event || typeof event !== "object") return;
1493
- const typed = event as { type?: string; message?: { role?: string; content?: unknown } };
1494
- if (typed.type === "message_end" && typed.message?.role === "assistant") {
1495
- assistantText = extractAssistantText(typed.message.content);
1496
- }
1497
- };
1498
- proc.stdout.on("data", (data) => {
1499
- buffer += (data as Buffer).toString();
1500
- const lines = buffer.split("\n");
1501
- buffer = lines.pop() || "";
1502
- for (const line of lines) processLine(line);
1503
- });
1504
- let stderr = "";
1505
- proc.stderr.on("data", (data) => { stderr += (data as Buffer).toString(); });
1506
- proc.on("error", (error) => settleReject(error));
1507
- proc.on("close", (code) => {
1508
- if (buffer.trim()) processLine(buffer.trim());
1509
- if ((code ?? 1) !== 0) {
1510
- settleReject(new Error(stderr.trim() || `Design assessment subprocess exited with code ${code ?? 1}.`));
1511
- return;
1512
- }
1513
- const jsonText = extractJsonObject(assistantText || buffer);
1514
- if (!jsonText) {
1515
- settleReject(new Error(`Design assessment subprocess did not return parseable JSON.\n${stderr}`));
1516
- return;
1517
- }
1518
- try {
1519
- const parsed = JSON.parse(jsonText) as { nodeTitle?: string; structuralPass?: boolean; findings?: DesignAssessmentFinding[] };
1520
- settleResolve({
1521
- nodeTitle: parsed.nodeTitle ?? nodeId,
1522
- structuralPass: parsed.structuralPass ?? true,
1523
- findings: Array.isArray(parsed.findings) ? parsed.findings : [],
1524
- });
1525
- } catch (err) {
1526
- settleReject(new Error(`Design assessment JSON was invalid: ${String(err)}`));
1527
- }
1528
- });
1529
- proc.stdin.write(prompt + "\n");
1530
- proc.stdin.end();
1531
- },
1532
- );
1533
- }
1534
-
1535
1426
  async function executeAssessDesign(
1536
1427
  pi: ExtensionAPI,
1537
1428
  ctx: AssessExecutionContext,
@@ -1601,23 +1492,69 @@ async function executeAssessDesign(
1601
1492
  }
1602
1493
 
1603
1494
  // Bridged / subprocess mode
1604
- let subResult: { findings: DesignAssessmentFinding[]; nodeTitle: string; structuralPass: boolean };
1605
- try {
1606
- subResult = await runDesignAssessmentSubprocess(cwd, nodeId, ctx.model?.id);
1607
- } catch (err) {
1608
- const msg = `Design assessment subprocess failed: ${String(err)}`;
1495
+ // Prefer an in-process deterministic fallback so design assessment does not depend
1496
+ // on a nested Omegon subprocess successfully loading a second extension graph.
1497
+ const tree = scanDesignDocs(path.join(cwd, "docs"));
1498
+ const node = tree.nodes.get(nodeId);
1499
+ if (!node) {
1500
+ const msg = `Design node '${nodeId}' not found under docs/.`;
1609
1501
  return makeAssessResult({
1610
1502
  subcommand: "design",
1611
1503
  args,
1612
1504
  ok: false,
1613
1505
  summary: msg,
1614
1506
  humanText: msg,
1615
- data: { reason: "subprocess_failed", nodeId },
1507
+ data: { reason: "node_not_found", nodeId },
1616
1508
  effects: [{ type: "view", content: msg }],
1617
1509
  });
1618
1510
  }
1619
-
1620
- const { findings, nodeTitle, structuralPass } = subResult;
1511
+ const sections = getNodeSections(node);
1512
+ const structuralFindings = runDesignStructuralCheck(nodeId, sections);
1513
+ const structuralPass = structuralFindings.length === 0;
1514
+ const documentBody = fs.readFileSync(node.filePath, "utf-8").toLowerCase();
1515
+ const normalizeCriterion = (value: string) => value.toLowerCase().replace(/[^a-z0-9]+/g, " ").trim();
1516
+ const includesCriterion = (value: string) => {
1517
+ const normalized = normalizeCriterion(value);
1518
+ if (!normalized) return true;
1519
+ return documentBody.includes(normalized) || normalized.split(/\s+/).filter(Boolean).every((part) => part.length < 4 || documentBody.includes(part));
1520
+ };
1521
+ const findings: DesignAssessmentFinding[] = [...structuralFindings];
1522
+ if (structuralPass) {
1523
+ sections.acceptanceCriteria.scenarios.forEach((scenario, index) => {
1524
+ const pass = includesCriterion(scenario.then);
1525
+ findings.push({
1526
+ type: "scenario",
1527
+ index,
1528
+ pass,
1529
+ finding: pass
1530
+ ? `Document addresses the scenario outcome: ${scenario.then}`
1531
+ : `Document does not clearly address the scenario outcome: ${scenario.then}`,
1532
+ });
1533
+ });
1534
+ sections.acceptanceCriteria.falsifiability.forEach((item, index) => {
1535
+ const pass = includesCriterion(item.condition);
1536
+ findings.push({
1537
+ type: "falsifiability",
1538
+ index,
1539
+ pass,
1540
+ finding: pass
1541
+ ? `Document addresses the falsifiability condition: ${item.condition}`
1542
+ : `Document does not clearly address the falsifiability condition: ${item.condition}`,
1543
+ });
1544
+ });
1545
+ sections.acceptanceCriteria.constraints.forEach((item, index) => {
1546
+ const pass = includesCriterion(item.text);
1547
+ findings.push({
1548
+ type: "constraint",
1549
+ index,
1550
+ pass,
1551
+ finding: pass
1552
+ ? `Document addresses the constraint: ${item.text}`
1553
+ : `Document does not clearly address the constraint: ${item.text}`,
1554
+ });
1555
+ });
1556
+ }
1557
+ const nodeTitle = node.title;
1621
1558
  const overallPass = structuralPass && findings.length > 0 && findings.every((f) => f.pass);
1622
1559
 
1623
1560
  const result: DesignAssessmentResult = { nodeId, pass: overallPass, structuralPass, findings };
@@ -598,7 +598,10 @@ export interface DirtyTreeClassificationOptions {
598
598
  volatileAllowlist?: string[];
599
599
  }
600
600
 
601
- export const DEFAULT_VOLATILE_ALLOWLIST = [".pi/memory/facts.jsonl"];
601
+ export const DEFAULT_VOLATILE_ALLOWLIST = [
602
+ ".pi/memory/facts.jsonl",
603
+ ".pi/runtime/operator-profile.json",
604
+ ];
602
605
 
603
606
  /**
604
607
  * Classify dirty-tree paths for preflight UX.
@@ -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
+ }
@@ -80,9 +80,9 @@ const EXECUTE_PARAMS = Type.Object({
80
80
  export type SlashCommandBridgeExecuteParams = Static<typeof EXECUTE_PARAMS>;
81
81
 
82
82
  function toArgString(args: readonly string[] | undefined): string {
83
- // Join args with spaces executors split on whitespace to extract subcommand and rest.
84
- // Individual args containing spaces are unsupported (none currently need them).
85
- return (args ?? []).join(" ");
83
+ // Preserve token boundaries for bridged execution (including args containing spaces).
84
+ // Structured executors in bridge mode can parse this JSON payload deterministically.
85
+ return JSON.stringify([...(args ?? [])]);
86
86
  }
87
87
 
88
88
  function summarize(command: string, args: readonly string[] | undefined): string {
@@ -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("pi", args, {
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
- // FTS5 match syntax
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
- if (mind) {
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 ? AND f.mind = ?
1071
+ WHERE facts_fts MATCH ?
1039
1072
  ORDER BY rank
1040
- `).all(ftsQuery, mind) as Fact[];
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.split(/\s+/).filter(t => t.length > 0).join(" AND ");
1082
+ const ftsQuery = buildSafeFtsQuery(query, "AND");
1054
1083
  if (!ftsQuery) return [];
1055
1084
 
1056
- if (mind) {
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.mind = ? AND f.status IN ('archived', 'superseded')
1098
+ WHERE facts_fts MATCH ? AND f.status IN ('archived', 'superseded')
1061
1099
  ORDER BY f.created_at DESC
1062
- `).all(ftsQuery, mind) as Fact[];
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 tokens = queryText.split(/\s+/).filter(t => t.length > 1);
1854
- if (tokens.length > 0) {
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
@@ -55,6 +55,7 @@ import { DEFAULT_CONFIG, type MemoryConfig, type LifecycleMemoryCandidate } from
55
55
  import { sanitizeCompactionText, shouldInterceptCompaction } from "./compaction-policy.ts";
56
56
  import { writeJsonlIfChanged } from "./jsonl-io.ts";
57
57
  import {
58
+ computeMemoryBudgetPolicy,
58
59
  createMemoryInjectionMetrics,
59
60
  estimateTokensFromChars,
60
61
  formatMemoryInjectionMetrics,
@@ -1501,17 +1502,13 @@ export default function (pi: ExtensionAPI) {
1501
1502
  const usage = ctx.getContextUsage();
1502
1503
 
1503
1504
  // Budget: reserve space for other context (design-tree, system prompt, etc.)
1504
- // Use 15% of total context as memory budget, with a floor and ceiling.
1505
- // Estimate total tokens from current usage: tokens / (percent/100).
1506
- const usedTokens = usage?.tokens ?? 0;
1507
- const usedPercent = usage?.percent ?? 0;
1508
- const estimatedTotalTokens = usedPercent > 0
1509
- ? Math.round(usedTokens / (usedPercent / 100))
1510
- : 200_000; // safe default
1511
- const MAX_MEMORY_CHARS = Math.min(
1512
- Math.max(Math.round(estimatedTotalTokens * 0.15 * 4), 4_000), // 15% of context, min 4K chars
1513
- 16_000, // absolute ceiling
1514
- );
1505
+ // Routine turns should carry a much smaller memory payload by default.
1506
+ const budgetPolicy = computeMemoryBudgetPolicy({
1507
+ usedTokens: usage?.tokens,
1508
+ usedPercent: usage?.percent,
1509
+ userText,
1510
+ });
1511
+ const MAX_MEMORY_CHARS = budgetPolicy.maxChars;
1515
1512
 
1516
1513
  const allFacts = store.getActiveFacts(mind);
1517
1514
  const injectedIds = new Set<string>();
@@ -1578,22 +1575,24 @@ export default function (pi: ExtensionAPI) {
1578
1575
  // Ensures every section has representation even without a matching query.
1579
1576
  // This is what bulk mode got right that old semantic mode missed.
1580
1577
  const FILL_SECTIONS = ["Constraints", "Known Issues", "Patterns & Conventions", "Specs", "Recent Work"] as const;
1581
- for (const section of FILL_SECTIONS) {
1582
- if (currentChars >= MAX_MEMORY_CHARS) break;
1583
- const sectionFacts = allFacts
1584
- .filter(f => f.section === section)
1585
- .sort((a, b) => b.confidence - a.confidence)
1586
- .slice(0, 3);
1587
- for (const f of sectionFacts) {
1588
- if (!tryAdd(f)) break;
1578
+ if (budgetPolicy.includeStructuralFill) {
1579
+ for (const section of FILL_SECTIONS) {
1580
+ if (currentChars >= MAX_MEMORY_CHARS) break;
1581
+ const sectionFacts = allFacts
1582
+ .filter(f => f.section === section)
1583
+ .sort((a, b) => b.confidence - a.confidence)
1584
+ .slice(0, 2);
1585
+ for (const f of sectionFacts) {
1586
+ if (!tryAdd(f)) break;
1587
+ }
1589
1588
  }
1590
1589
  }
1591
1590
 
1592
1591
  // --- Tier 6: Recency fill — most recently reinforced not yet included ---
1593
- if (currentChars < MAX_MEMORY_CHARS) {
1592
+ if (budgetPolicy.includeStructuralFill && currentChars < MAX_MEMORY_CHARS) {
1594
1593
  const recentFacts = [...allFacts]
1595
1594
  .sort((a, b) => new Date(b.last_reinforced).getTime() - new Date(a.last_reinforced).getTime())
1596
- .slice(0, 20);
1595
+ .slice(0, 12);
1597
1596
  for (const f of recentFacts) {
1598
1597
  if (!tryAdd(f)) break;
1599
1598
  }
@@ -1605,7 +1604,7 @@ export default function (pi: ExtensionAPI) {
1605
1604
  // --- Global knowledge: semantic-gated, only when query is available ---
1606
1605
  let globalSection = "";
1607
1606
  let injectedGlobalFactCount = 0;
1608
- if (globalStore && userText.length > 10) {
1607
+ if (budgetPolicy.includeGlobalFacts && globalStore && userText.length > 10) {
1609
1608
  const globalMind = globalStore.getActiveMind() ?? "default";
1610
1609
  const globalFactCount = globalStore.countActiveFacts(globalMind);
1611
1610
  if (globalFactCount > 0) {
@@ -1633,7 +1632,7 @@ export default function (pi: ExtensionAPI) {
1633
1632
  let episodeSection = "";
1634
1633
  let injectedEpisodeCount = 0;
1635
1634
  const episodeCount = store.countEpisodes(mind);
1636
- if (episodeCount > 0) {
1635
+ if (budgetPolicy.includeEpisode && episodeCount > 0) {
1637
1636
  const recentEpisodes = store.getEpisodes(mind, 1);
1638
1637
  if (recentEpisodes.length > 0) {
1639
1638
  injectedEpisodeCount = recentEpisodes.length;
@@ -17,6 +17,38 @@ export interface MemoryInjectionMetrics {
17
17
  estimatedVsObservedDelta?: number | null;
18
18
  }
19
19
 
20
+ export interface MemoryBudgetPolicy {
21
+ maxChars: number;
22
+ includeStructuralFill: boolean;
23
+ includeGlobalFacts: boolean;
24
+ includeEpisode: boolean;
25
+ }
26
+
27
+ export function computeMemoryBudgetPolicy(input: {
28
+ usedTokens?: number | null;
29
+ usedPercent?: number | null;
30
+ userText?: string;
31
+ }): MemoryBudgetPolicy {
32
+ const usedTokens = input.usedTokens ?? 0;
33
+ const usedPercent = input.usedPercent ?? 0;
34
+ const userText = input.userText ?? "";
35
+ const estimatedTotalTokens = usedPercent > 0
36
+ ? Math.round(usedTokens / (usedPercent / 100))
37
+ : 200_000;
38
+ const maxChars = Math.min(
39
+ Math.max(Math.round(estimatedTotalTokens * 0.08 * 4), 2_000),
40
+ 8_000,
41
+ );
42
+ const trimmed = userText.trim();
43
+ const signalLength = trimmed.length;
44
+ const signalWords = trimmed.split(/\s+/).filter(Boolean).length;
45
+ const includeStructuralFill = signalLength >= 24 || signalWords >= 5;
46
+ const includeGlobalFacts = signalLength >= 48 || signalWords >= 8;
47
+ const includeEpisode = signalLength >= 64 || signalWords >= 10;
48
+
49
+ return { maxChars, includeStructuralFill, includeGlobalFacts, includeEpisode };
50
+ }
51
+
20
52
  export function estimateTokensFromChars(content: string): number {
21
53
  return Math.round(content.length / 4);
22
54
  }
@@ -0,0 +1,534 @@
1
+ {
2
+ "name": "@omegon/composition",
3
+ "version": "0.1.0",
4
+ "lockfileVersion": 3,
5
+ "requires": true,
6
+ "packages": {
7
+ "": {
8
+ "name": "@omegon/composition",
9
+ "version": "0.1.0",
10
+ "dependencies": {
11
+ "@resvg/resvg-js": "^2.6.2",
12
+ "gifenc": "^1.0.3",
13
+ "jiti": "^2.4.2",
14
+ "react": "^18.3.1",
15
+ "react-dom": "^18.3.1",
16
+ "satori": "^0.11.2"
17
+ },
18
+ "devDependencies": {
19
+ "@types/react": "^18.3.12",
20
+ "@types/react-dom": "^18.3.1"
21
+ }
22
+ },
23
+ "node_modules/@resvg/resvg-js": {
24
+ "version": "2.6.2",
25
+ "resolved": "https://registry.npmjs.org/@resvg/resvg-js/-/resvg-js-2.6.2.tgz",
26
+ "integrity": "sha512-xBaJish5OeGmniDj9cW5PRa/PtmuVU3ziqrbr5xJj901ZDN4TosrVaNZpEiLZAxdfnhAe7uQ7QFWfjPe9d9K2Q==",
27
+ "license": "MPL-2.0",
28
+ "engines": {
29
+ "node": ">= 10"
30
+ },
31
+ "optionalDependencies": {
32
+ "@resvg/resvg-js-android-arm-eabi": "2.6.2",
33
+ "@resvg/resvg-js-android-arm64": "2.6.2",
34
+ "@resvg/resvg-js-darwin-arm64": "2.6.2",
35
+ "@resvg/resvg-js-darwin-x64": "2.6.2",
36
+ "@resvg/resvg-js-linux-arm-gnueabihf": "2.6.2",
37
+ "@resvg/resvg-js-linux-arm64-gnu": "2.6.2",
38
+ "@resvg/resvg-js-linux-arm64-musl": "2.6.2",
39
+ "@resvg/resvg-js-linux-x64-gnu": "2.6.2",
40
+ "@resvg/resvg-js-linux-x64-musl": "2.6.2",
41
+ "@resvg/resvg-js-win32-arm64-msvc": "2.6.2",
42
+ "@resvg/resvg-js-win32-ia32-msvc": "2.6.2",
43
+ "@resvg/resvg-js-win32-x64-msvc": "2.6.2"
44
+ }
45
+ },
46
+ "node_modules/@resvg/resvg-js-android-arm-eabi": {
47
+ "version": "2.6.2",
48
+ "resolved": "https://registry.npmjs.org/@resvg/resvg-js-android-arm-eabi/-/resvg-js-android-arm-eabi-2.6.2.tgz",
49
+ "integrity": "sha512-FrJibrAk6v29eabIPgcTUMPXiEz8ssrAk7TXxsiZzww9UTQ1Z5KAbFJs+Z0Ez+VZTYgnE5IQJqBcoSiMebtPHA==",
50
+ "cpu": [
51
+ "arm"
52
+ ],
53
+ "license": "MPL-2.0",
54
+ "optional": true,
55
+ "os": [
56
+ "android"
57
+ ],
58
+ "engines": {
59
+ "node": ">= 10"
60
+ }
61
+ },
62
+ "node_modules/@resvg/resvg-js-android-arm64": {
63
+ "version": "2.6.2",
64
+ "resolved": "https://registry.npmjs.org/@resvg/resvg-js-android-arm64/-/resvg-js-android-arm64-2.6.2.tgz",
65
+ "integrity": "sha512-VcOKezEhm2VqzXpcIJoITuvUS/fcjIw5NA/w3tjzWyzmvoCdd+QXIqy3FBGulWdClvp4g+IfUemigrkLThSjAQ==",
66
+ "cpu": [
67
+ "arm64"
68
+ ],
69
+ "license": "MPL-2.0",
70
+ "optional": true,
71
+ "os": [
72
+ "android"
73
+ ],
74
+ "engines": {
75
+ "node": ">= 10"
76
+ }
77
+ },
78
+ "node_modules/@resvg/resvg-js-darwin-arm64": {
79
+ "version": "2.6.2",
80
+ "resolved": "https://registry.npmjs.org/@resvg/resvg-js-darwin-arm64/-/resvg-js-darwin-arm64-2.6.2.tgz",
81
+ "integrity": "sha512-nmok2LnAd6nLUKI16aEB9ydMC6Lidiiq2m1nEBDR1LaaP7FGs4AJ90qDraxX+CWlVuRlvNjyYJTNv8qFjtL9+A==",
82
+ "cpu": [
83
+ "arm64"
84
+ ],
85
+ "license": "MPL-2.0",
86
+ "optional": true,
87
+ "os": [
88
+ "darwin"
89
+ ],
90
+ "engines": {
91
+ "node": ">= 10"
92
+ }
93
+ },
94
+ "node_modules/@resvg/resvg-js-darwin-x64": {
95
+ "version": "2.6.2",
96
+ "resolved": "https://registry.npmjs.org/@resvg/resvg-js-darwin-x64/-/resvg-js-darwin-x64-2.6.2.tgz",
97
+ "integrity": "sha512-GInyZLjgWDfsVT6+SHxQVRwNzV0AuA1uqGsOAW+0th56J7Nh6bHHKXHBWzUrihxMetcFDmQMAX1tZ1fZDYSRsw==",
98
+ "cpu": [
99
+ "x64"
100
+ ],
101
+ "license": "MPL-2.0",
102
+ "optional": true,
103
+ "os": [
104
+ "darwin"
105
+ ],
106
+ "engines": {
107
+ "node": ">= 10"
108
+ }
109
+ },
110
+ "node_modules/@resvg/resvg-js-linux-arm-gnueabihf": {
111
+ "version": "2.6.2",
112
+ "resolved": "https://registry.npmjs.org/@resvg/resvg-js-linux-arm-gnueabihf/-/resvg-js-linux-arm-gnueabihf-2.6.2.tgz",
113
+ "integrity": "sha512-YIV3u/R9zJbpqTTNwTZM5/ocWetDKGsro0SWp70eGEM9eV2MerWyBRZnQIgzU3YBnSBQ1RcxRZvY/UxwESfZIw==",
114
+ "cpu": [
115
+ "arm"
116
+ ],
117
+ "license": "MPL-2.0",
118
+ "optional": true,
119
+ "os": [
120
+ "linux"
121
+ ],
122
+ "engines": {
123
+ "node": ">= 10"
124
+ }
125
+ },
126
+ "node_modules/@resvg/resvg-js-linux-arm64-gnu": {
127
+ "version": "2.6.2",
128
+ "resolved": "https://registry.npmjs.org/@resvg/resvg-js-linux-arm64-gnu/-/resvg-js-linux-arm64-gnu-2.6.2.tgz",
129
+ "integrity": "sha512-zc2BlJSim7YR4FZDQ8OUoJg5holYzdiYMeobb9pJuGDidGL9KZUv7SbiD4E8oZogtYY42UZEap7dqkkYuA91pg==",
130
+ "cpu": [
131
+ "arm64"
132
+ ],
133
+ "license": "MPL-2.0",
134
+ "optional": true,
135
+ "os": [
136
+ "linux"
137
+ ],
138
+ "engines": {
139
+ "node": ">= 10"
140
+ }
141
+ },
142
+ "node_modules/@resvg/resvg-js-linux-arm64-musl": {
143
+ "version": "2.6.2",
144
+ "resolved": "https://registry.npmjs.org/@resvg/resvg-js-linux-arm64-musl/-/resvg-js-linux-arm64-musl-2.6.2.tgz",
145
+ "integrity": "sha512-3h3dLPWNgSsD4lQBJPb4f+kvdOSJHa5PjTYVsWHxLUzH4IFTJUAnmuWpw4KqyQ3NA5QCyhw4TWgxk3jRkQxEKg==",
146
+ "cpu": [
147
+ "arm64"
148
+ ],
149
+ "license": "MPL-2.0",
150
+ "optional": true,
151
+ "os": [
152
+ "linux"
153
+ ],
154
+ "engines": {
155
+ "node": ">= 10"
156
+ }
157
+ },
158
+ "node_modules/@resvg/resvg-js-linux-x64-gnu": {
159
+ "version": "2.6.2",
160
+ "resolved": "https://registry.npmjs.org/@resvg/resvg-js-linux-x64-gnu/-/resvg-js-linux-x64-gnu-2.6.2.tgz",
161
+ "integrity": "sha512-IVUe+ckIerA7xMZ50duAZzwf1U7khQe2E0QpUxu5MBJNao5RqC0zwV/Zm965vw6D3gGFUl7j4m+oJjubBVoftw==",
162
+ "cpu": [
163
+ "x64"
164
+ ],
165
+ "license": "MPL-2.0",
166
+ "optional": true,
167
+ "os": [
168
+ "linux"
169
+ ],
170
+ "engines": {
171
+ "node": ">= 10"
172
+ }
173
+ },
174
+ "node_modules/@resvg/resvg-js-linux-x64-musl": {
175
+ "version": "2.6.2",
176
+ "resolved": "https://registry.npmjs.org/@resvg/resvg-js-linux-x64-musl/-/resvg-js-linux-x64-musl-2.6.2.tgz",
177
+ "integrity": "sha512-UOf83vqTzoYQO9SZ0fPl2ZIFtNIz/Rr/y+7X8XRX1ZnBYsQ/tTb+cj9TE+KHOdmlTFBxhYzVkP2lRByCzqi4jQ==",
178
+ "cpu": [
179
+ "x64"
180
+ ],
181
+ "license": "MPL-2.0",
182
+ "optional": true,
183
+ "os": [
184
+ "linux"
185
+ ],
186
+ "engines": {
187
+ "node": ">= 10"
188
+ }
189
+ },
190
+ "node_modules/@resvg/resvg-js-win32-arm64-msvc": {
191
+ "version": "2.6.2",
192
+ "resolved": "https://registry.npmjs.org/@resvg/resvg-js-win32-arm64-msvc/-/resvg-js-win32-arm64-msvc-2.6.2.tgz",
193
+ "integrity": "sha512-7C/RSgCa+7vqZ7qAbItfiaAWhyRSoD4l4BQAbVDqRRsRgY+S+hgS3in0Rxr7IorKUpGE69X48q6/nOAuTJQxeQ==",
194
+ "cpu": [
195
+ "arm64"
196
+ ],
197
+ "license": "MPL-2.0",
198
+ "optional": true,
199
+ "os": [
200
+ "win32"
201
+ ],
202
+ "engines": {
203
+ "node": ">= 10"
204
+ }
205
+ },
206
+ "node_modules/@resvg/resvg-js-win32-ia32-msvc": {
207
+ "version": "2.6.2",
208
+ "resolved": "https://registry.npmjs.org/@resvg/resvg-js-win32-ia32-msvc/-/resvg-js-win32-ia32-msvc-2.6.2.tgz",
209
+ "integrity": "sha512-har4aPAlvjnLcil40AC77YDIk6loMawuJwFINEM7n0pZviwMkMvjb2W5ZirsNOZY4aDbo5tLx0wNMREp5Brk+w==",
210
+ "cpu": [
211
+ "ia32"
212
+ ],
213
+ "license": "MPL-2.0",
214
+ "optional": true,
215
+ "os": [
216
+ "win32"
217
+ ],
218
+ "engines": {
219
+ "node": ">= 10"
220
+ }
221
+ },
222
+ "node_modules/@resvg/resvg-js-win32-x64-msvc": {
223
+ "version": "2.6.2",
224
+ "resolved": "https://registry.npmjs.org/@resvg/resvg-js-win32-x64-msvc/-/resvg-js-win32-x64-msvc-2.6.2.tgz",
225
+ "integrity": "sha512-ZXtYhtUr5SSaBrUDq7DiyjOFJqBVL/dOBN7N/qmi/pO0IgiWW/f/ue3nbvu9joWE5aAKDoIzy/CxsY0suwGosQ==",
226
+ "cpu": [
227
+ "x64"
228
+ ],
229
+ "license": "MPL-2.0",
230
+ "optional": true,
231
+ "os": [
232
+ "win32"
233
+ ],
234
+ "engines": {
235
+ "node": ">= 10"
236
+ }
237
+ },
238
+ "node_modules/@shuding/opentype.js": {
239
+ "version": "1.4.0-beta.0",
240
+ "resolved": "https://registry.npmjs.org/@shuding/opentype.js/-/opentype.js-1.4.0-beta.0.tgz",
241
+ "integrity": "sha512-3NgmNyH3l/Hv6EvsWJbsvpcpUba6R8IREQ83nH83cyakCw7uM1arZKNfHwv1Wz6jgqrF/j4x5ELvR6PnK9nTcA==",
242
+ "license": "MIT",
243
+ "dependencies": {
244
+ "fflate": "^0.7.3",
245
+ "string.prototype.codepointat": "^0.2.1"
246
+ },
247
+ "bin": {
248
+ "ot": "bin/ot"
249
+ },
250
+ "engines": {
251
+ "node": ">= 8.0.0"
252
+ }
253
+ },
254
+ "node_modules/@types/prop-types": {
255
+ "version": "15.7.15",
256
+ "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
257
+ "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==",
258
+ "dev": true,
259
+ "license": "MIT"
260
+ },
261
+ "node_modules/@types/react": {
262
+ "version": "18.3.28",
263
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.28.tgz",
264
+ "integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==",
265
+ "dev": true,
266
+ "license": "MIT",
267
+ "dependencies": {
268
+ "@types/prop-types": "*",
269
+ "csstype": "^3.2.2"
270
+ }
271
+ },
272
+ "node_modules/@types/react-dom": {
273
+ "version": "18.3.7",
274
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz",
275
+ "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==",
276
+ "dev": true,
277
+ "license": "MIT",
278
+ "peerDependencies": {
279
+ "@types/react": "^18.0.0"
280
+ }
281
+ },
282
+ "node_modules/base64-js": {
283
+ "version": "0.0.8",
284
+ "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-0.0.8.tgz",
285
+ "integrity": "sha512-3XSA2cR/h/73EzlXXdU6YNycmYI7+kicTxks4eJg2g39biHR84slg2+des+p7iHYhbRg/udIS4TD53WabcOUkw==",
286
+ "license": "MIT",
287
+ "engines": {
288
+ "node": ">= 0.4"
289
+ }
290
+ },
291
+ "node_modules/camelize": {
292
+ "version": "1.0.1",
293
+ "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz",
294
+ "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==",
295
+ "license": "MIT",
296
+ "funding": {
297
+ "url": "https://github.com/sponsors/ljharb"
298
+ }
299
+ },
300
+ "node_modules/color-name": {
301
+ "version": "1.1.4",
302
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
303
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
304
+ "license": "MIT"
305
+ },
306
+ "node_modules/css-background-parser": {
307
+ "version": "0.1.0",
308
+ "resolved": "https://registry.npmjs.org/css-background-parser/-/css-background-parser-0.1.0.tgz",
309
+ "integrity": "sha512-2EZLisiZQ+7m4wwur/qiYJRniHX4K5Tc9w93MT3AS0WS1u5kaZ4FKXlOTBhOjc+CgEgPiGY+fX1yWD8UwpEqUA==",
310
+ "license": "MIT"
311
+ },
312
+ "node_modules/css-box-shadow": {
313
+ "version": "1.0.0-3",
314
+ "resolved": "https://registry.npmjs.org/css-box-shadow/-/css-box-shadow-1.0.0-3.tgz",
315
+ "integrity": "sha512-9jaqR6e7Ohds+aWwmhe6wILJ99xYQbfmK9QQB9CcMjDbTxPZjwEmUQpU91OG05Xgm8BahT5fW+svbsQGjS/zPg==",
316
+ "license": "MIT"
317
+ },
318
+ "node_modules/css-color-keywords": {
319
+ "version": "1.0.0",
320
+ "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz",
321
+ "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==",
322
+ "license": "ISC",
323
+ "engines": {
324
+ "node": ">=4"
325
+ }
326
+ },
327
+ "node_modules/css-gradient-parser": {
328
+ "version": "0.0.16",
329
+ "resolved": "https://registry.npmjs.org/css-gradient-parser/-/css-gradient-parser-0.0.16.tgz",
330
+ "integrity": "sha512-3O5QdqgFRUbXvK1x5INf1YkBz1UKSWqrd63vWsum8MNHDBYD5urm3QtxZbKU259OrEXNM26lP/MPY3d1IGkBgA==",
331
+ "license": "MIT",
332
+ "engines": {
333
+ "node": ">=16"
334
+ }
335
+ },
336
+ "node_modules/css-to-react-native": {
337
+ "version": "3.2.0",
338
+ "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz",
339
+ "integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==",
340
+ "license": "MIT",
341
+ "dependencies": {
342
+ "camelize": "^1.0.0",
343
+ "css-color-keywords": "^1.0.0",
344
+ "postcss-value-parser": "^4.0.2"
345
+ }
346
+ },
347
+ "node_modules/csstype": {
348
+ "version": "3.2.3",
349
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
350
+ "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
351
+ "dev": true,
352
+ "license": "MIT"
353
+ },
354
+ "node_modules/emoji-regex": {
355
+ "version": "10.6.0",
356
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz",
357
+ "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==",
358
+ "license": "MIT"
359
+ },
360
+ "node_modules/escape-html": {
361
+ "version": "1.0.3",
362
+ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
363
+ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
364
+ "license": "MIT"
365
+ },
366
+ "node_modules/fflate": {
367
+ "version": "0.7.4",
368
+ "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.7.4.tgz",
369
+ "integrity": "sha512-5u2V/CDW15QM1XbbgS+0DfPxVB+jUKhWEKuuFuHncbk3tEEqzmoXL+2KyOFuKGqOnmdIy0/davWF1CkuwtibCw==",
370
+ "license": "MIT"
371
+ },
372
+ "node_modules/gifenc": {
373
+ "version": "1.0.3",
374
+ "resolved": "https://registry.npmjs.org/gifenc/-/gifenc-1.0.3.tgz",
375
+ "integrity": "sha512-xdr6AdrfGBcfzncONUOlXMBuc5wJDtOueE3c5rdG0oNgtINLD+f2iFZltrBRZYzACRbKr+mSVU/x98zv2u3jmw==",
376
+ "license": "MIT"
377
+ },
378
+ "node_modules/hex-rgb": {
379
+ "version": "4.3.0",
380
+ "resolved": "https://registry.npmjs.org/hex-rgb/-/hex-rgb-4.3.0.tgz",
381
+ "integrity": "sha512-Ox1pJVrDCyGHMG9CFg1tmrRUMRPRsAWYc/PinY0XzJU4K7y7vjNoLKIQ7BR5UJMCxNN8EM1MNDmHWA/B3aZUuw==",
382
+ "license": "MIT",
383
+ "engines": {
384
+ "node": ">=6"
385
+ },
386
+ "funding": {
387
+ "url": "https://github.com/sponsors/sindresorhus"
388
+ }
389
+ },
390
+ "node_modules/jiti": {
391
+ "version": "2.6.1",
392
+ "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz",
393
+ "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==",
394
+ "license": "MIT",
395
+ "bin": {
396
+ "jiti": "lib/jiti-cli.mjs"
397
+ }
398
+ },
399
+ "node_modules/js-tokens": {
400
+ "version": "4.0.0",
401
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
402
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
403
+ "license": "MIT"
404
+ },
405
+ "node_modules/linebreak": {
406
+ "version": "1.1.0",
407
+ "resolved": "https://registry.npmjs.org/linebreak/-/linebreak-1.1.0.tgz",
408
+ "integrity": "sha512-MHp03UImeVhB7XZtjd0E4n6+3xr5Dq/9xI/5FptGk5FrbDR3zagPa2DS6U8ks/3HjbKWG9Q1M2ufOzxV2qLYSQ==",
409
+ "license": "MIT",
410
+ "dependencies": {
411
+ "base64-js": "0.0.8",
412
+ "unicode-trie": "^2.0.0"
413
+ }
414
+ },
415
+ "node_modules/loose-envify": {
416
+ "version": "1.4.0",
417
+ "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
418
+ "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
419
+ "license": "MIT",
420
+ "dependencies": {
421
+ "js-tokens": "^3.0.0 || ^4.0.0"
422
+ },
423
+ "bin": {
424
+ "loose-envify": "cli.js"
425
+ }
426
+ },
427
+ "node_modules/pako": {
428
+ "version": "0.2.9",
429
+ "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz",
430
+ "integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==",
431
+ "license": "MIT"
432
+ },
433
+ "node_modules/parse-css-color": {
434
+ "version": "0.2.1",
435
+ "resolved": "https://registry.npmjs.org/parse-css-color/-/parse-css-color-0.2.1.tgz",
436
+ "integrity": "sha512-bwS/GGIFV3b6KS4uwpzCFj4w297Yl3uqnSgIPsoQkx7GMLROXfMnWvxfNkL0oh8HVhZA4hvJoEoEIqonfJ3BWg==",
437
+ "license": "MIT",
438
+ "dependencies": {
439
+ "color-name": "^1.1.4",
440
+ "hex-rgb": "^4.1.0"
441
+ }
442
+ },
443
+ "node_modules/postcss-value-parser": {
444
+ "version": "4.2.0",
445
+ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
446
+ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
447
+ "license": "MIT"
448
+ },
449
+ "node_modules/react": {
450
+ "version": "18.3.1",
451
+ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
452
+ "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
453
+ "license": "MIT",
454
+ "dependencies": {
455
+ "loose-envify": "^1.1.0"
456
+ },
457
+ "engines": {
458
+ "node": ">=0.10.0"
459
+ }
460
+ },
461
+ "node_modules/react-dom": {
462
+ "version": "18.3.1",
463
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
464
+ "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
465
+ "license": "MIT",
466
+ "dependencies": {
467
+ "loose-envify": "^1.1.0",
468
+ "scheduler": "^0.23.2"
469
+ },
470
+ "peerDependencies": {
471
+ "react": "^18.3.1"
472
+ }
473
+ },
474
+ "node_modules/satori": {
475
+ "version": "0.11.3",
476
+ "resolved": "https://registry.npmjs.org/satori/-/satori-0.11.3.tgz",
477
+ "integrity": "sha512-Wg7sls0iYAEETzi9YYcY16QVIqXjZT06XjkwondC5CGhw1mhmgKBCub8cCmkxdl/naXXQD+m29CFgn8pwtYCnA==",
478
+ "license": "MPL-2.0",
479
+ "dependencies": {
480
+ "@shuding/opentype.js": "1.4.0-beta.0",
481
+ "css-background-parser": "^0.1.0",
482
+ "css-box-shadow": "1.0.0-3",
483
+ "css-gradient-parser": "^0.0.16",
484
+ "css-to-react-native": "^3.0.0",
485
+ "emoji-regex": "^10.2.1",
486
+ "escape-html": "^1.0.3",
487
+ "linebreak": "^1.1.0",
488
+ "parse-css-color": "^0.2.1",
489
+ "postcss-value-parser": "^4.2.0",
490
+ "yoga-wasm-web": "^0.3.3"
491
+ },
492
+ "engines": {
493
+ "node": ">=16"
494
+ }
495
+ },
496
+ "node_modules/scheduler": {
497
+ "version": "0.23.2",
498
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
499
+ "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
500
+ "license": "MIT",
501
+ "dependencies": {
502
+ "loose-envify": "^1.1.0"
503
+ }
504
+ },
505
+ "node_modules/string.prototype.codepointat": {
506
+ "version": "0.2.1",
507
+ "resolved": "https://registry.npmjs.org/string.prototype.codepointat/-/string.prototype.codepointat-0.2.1.tgz",
508
+ "integrity": "sha512-2cBVCj6I4IOvEnjgO/hWqXjqBGsY+zwPmHl12Srk9IXSZ56Jwwmy+66XO5Iut/oQVR7t5ihYdLB0GMa4alEUcg==",
509
+ "license": "MIT"
510
+ },
511
+ "node_modules/tiny-inflate": {
512
+ "version": "1.0.3",
513
+ "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz",
514
+ "integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==",
515
+ "license": "MIT"
516
+ },
517
+ "node_modules/unicode-trie": {
518
+ "version": "2.0.0",
519
+ "resolved": "https://registry.npmjs.org/unicode-trie/-/unicode-trie-2.0.0.tgz",
520
+ "integrity": "sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==",
521
+ "license": "MIT",
522
+ "dependencies": {
523
+ "pako": "^0.2.5",
524
+ "tiny-inflate": "^1.0.0"
525
+ }
526
+ },
527
+ "node_modules/yoga-wasm-web": {
528
+ "version": "0.3.3",
529
+ "resolved": "https://registry.npmjs.org/yoga-wasm-web/-/yoga-wasm-web-0.3.3.tgz",
530
+ "integrity": "sha512-N+d4UJSJbt/R3wqY7Coqs5pcV0aUj2j9IaQ3rNj9bVCLld8tTGKRa2USARjnvZJWVx1NDmQev8EknoczaOQDOA==",
531
+ "license": "MIT"
532
+ }
533
+ }
534
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "omegon",
3
- "version": "0.6.5",
3
+ "version": "0.6.7",
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",
@@ -32,6 +32,9 @@
32
32
  "url": "https://github.com/styrene-lab/omegon/issues"
33
33
  },
34
34
  "homepage": "https://github.com/styrene-lab/omegon#readme",
35
+ "engines": {
36
+ "node": ">=20.0.0"
37
+ },
35
38
  "pi": {
36
39
  "extensions": [
37
40
  "./extensions/bootstrap",
@@ -72,10 +75,10 @@
72
75
  ]
73
76
  },
74
77
  "dependencies": {
75
- "@styrene-lab/pi-coding-agent": "0.58.1-cwilson613.1",
76
- "@styrene-lab/pi-agent-core": "0.58.1",
77
- "@styrene-lab/pi-ai": "0.58.1-cwilson613.1",
78
- "@styrene-lab/pi-tui": "0.58.1-cwilson613.1",
78
+ "@styrene-lab/pi-coding-agent": "file:./vendor/pi-mono/packages/coding-agent",
79
+ "@styrene-lab/pi-agent-core": "file:./vendor/pi-mono/packages/agent",
80
+ "@styrene-lab/pi-ai": "file:./vendor/pi-mono/packages/ai",
81
+ "@styrene-lab/pi-tui": "file:./vendor/pi-mono/packages/tui",
79
82
  "@modelcontextprotocol/sdk": "^1.12.1",
80
83
  "@resvg/resvg-js": "^2.6.2",
81
84
  "@sinclair/typebox": "^0.34.48",
@@ -17,6 +17,16 @@
17
17
  #
18
18
  # Only acts during global installs (npm_config_global=true).
19
19
 
20
+ node_major="$(node -p 'process.versions.node.split(".")[0]' 2>/dev/null || echo 0)"
21
+ if [ "$node_major" -lt 20 ]; then
22
+ echo ""
23
+ echo " omegon: Unsupported Node.js runtime detected ($(node -v 2>/dev/null || echo unknown))."
24
+ echo " omegon: Omegon requires Node.js 20 or later because bundled pi-tui uses modern Unicode regex features."
25
+ echo " omegon: Upgrade Node.js to 20+ and retry the install/update."
26
+ echo ""
27
+ exit 1
28
+ fi
29
+
20
30
  if [ "$npm_config_global" != "true" ]; then
21
31
  exit 0
22
32
  fi
package/settings.json ADDED
@@ -0,0 +1,7 @@
1
+ {
2
+ "lastChangelogVersion": "0.58.1-cwilson613.1",
3
+ "theme": "alpharius",
4
+ "defaultProvider": "anthropic",
5
+ "defaultModel": "claude-opus-4-6",
6
+ "defaultThinkingLevel": "low"
7
+ }