@intent-driven/runtime-local 0.2.0 → 0.3.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/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Standalone Fold-runtime для локального quickstart'а. Принимает ontology.js (сгенерированный `idf full-bootstrap`), поднимает HTTP-сервер с эндпоинтами, которые ожидает `@intent-driven/mcp-server` для подключения к Claude Desktop / Cursor / Zed.
4
4
 
5
- > **Статус:** v0.1skeleton. Поддерживает `/api/health`, `/api/typemap`, `/api/agent/:domain/schema` (discovery). Полный agent/exec, approvals lifecycle, /api/state/* — в следующих релизах (см. roadmap fold-15min).
5
+ > **Статус:** v0.3stateful Φ через `@intent-driven/engine`. Поддерживает `/api/health`, `/api/typemap`, `/api/agent/:domain/schema` (discovery), `/api/state/current`, `/api/agent/:domain/world` (fold снимок). agent/exec + approvals lifecycle — в следующих релизах (см. roadmap fold-15min).
6
6
 
7
7
  ## Зачем
8
8
 
@@ -12,7 +12,10 @@ Standalone Fold-runtime для локального quickstart'а. Приним
12
12
  idf serve --ontology ./ontology.js # планируется в @intent-driven/cli
13
13
  # или программно:
14
14
  import { createRuntime } from '@intent-driven/runtime-local';
15
- const runtime = createRuntime({ ontology: require('./ontology.js') });
15
+ const runtime = createRuntime({
16
+ ontology: require('./ontology.js'),
17
+ seed: require('./seed.json'), // опц. — массив эффектов
18
+ });
16
19
  await runtime.start();
17
20
  ```
18
21
 
@@ -22,7 +25,7 @@ await runtime.start();
22
25
  npm install @intent-driven/runtime-local
23
26
  ```
24
27
 
25
- Peer-зависимость: `@intent-driven/core@>=0.49.0` (нужен для будущих релизов с validator/fold; в v0.1 не требуется в runtime, но указан как peer для forward-compat).
28
+ Peer-зависимость: `@intent-driven/core@>=0.49.0` (через `@intent-driven/engine`).
26
29
 
27
30
  ## API
28
31
 
@@ -33,33 +36,36 @@ Peer-зависимость: `@intent-driven/core@>=0.49.0` (нужен для
33
36
  | Опция | Тип | Default | Описание |
34
37
  |---|---|---|---|
35
38
  | `ontology` | object | **required** | IDF ontology — `{ entities, intents, roles, projections, ... }`. |
39
+ | `seed` | array | `[]` | Pre-confirmed эффекты для начального состояния Φ. Каждый — `{id, intent_id, alpha, target, context, value?, status: 'confirmed', created_at}`. Primitive `value` (строки/числа) нормализуются в JSON-encoded автоматически. |
36
40
  | `port` | number | `3001` | Порт. `0` = random (для тестов). |
37
41
  | `host` | string | `'127.0.0.1'` | Bind-адрес. |
38
42
 
39
- Возвращает `{ app, ontology, domain, start(), stop() }`.
43
+ Возвращает `{ app, ontology, domain, engine, persistence, start(), stop() }`. `engine` — instance `@intent-driven/engine`, для прямого использования (e.g., тесты).
40
44
 
41
45
  ### `runtime.start()` → `Promise<{url, port, host}>`
42
46
 
43
- Поднимает Express-сервер. Возвращает actual URL/port (полезно при `port: 0`).
47
+ Поднимает Express-сервер. Загружает `seed` в persistence перед listen. Возвращает actual URL/port (полезно при `port: 0`).
44
48
 
45
49
  ### `runtime.stop()` → `Promise<void>`
46
50
 
47
51
  Закрывает сервер.
48
52
 
49
- ## Эндпоинты (v0.1)
53
+ ## Эндпоинты
50
54
 
51
55
  | Метод | Путь | Назначение |
52
56
  |---|---|---|
53
57
  | `GET` | `/api/health` | Liveness check для `idf doctor` и MCP-server. Возвращает `{status, domain, runtime, version}`. |
54
58
  | `GET` | `/api/typemap` | Сводка ontology: entities/intents/roles names. |
55
- | `GET` | `/api/agent/:domain/schema` | Full ontology snapshot для MCP discovery. 503 если domain не совпадает с зарегистрированным. |
59
+ | `GET` | `/api/agent/:domain/schema` | Full ontology snapshot для MCP discovery. 503 если domain не совпадает. |
60
+ | `GET` | `/api/state/current` | Folded world snapshot — `{domain, world: {products: [...], orders: [...]}}`. |
61
+ | `GET` | `/api/agent/:domain/world` | Тот же world, что и `/api/state/current`. Этот endpoint вызывает `@intent-driven/mcp-server` для resource-чтения. 503 при mismatch domain. |
56
62
 
57
63
  ## Roadmap (следующие PR)
58
64
 
59
- - v0.2: in-memory Φ-store + `GET /api/state/at` + `GET /api/state/diff`
60
- - v0.3: `POST /api/agent/:domain/exec/:intentId` ingest с invariant validation, ApprovalRequest auto-injection через `lifecycleAugmenter`
61
- - v0.4: `GET /api/approvals/pending` + `POST /api/approvals/:id/approve|reject` + timer-driven expiry
62
- - v0.5: SSE-стрим `/api/approvals/stream` для `idf approvals --watch`
65
+ - v0.4: `POST /api/agent/:domain/exec/:intentId` ingest через `engine.submit` с invariant validation, ApprovalRequest auto-injection через `lifecycleAugmenter`
66
+ - v0.5: `GET /api/approvals/pending` + `POST /api/approvals/:id/approve|reject` + timer-driven expiry
67
+ - v0.6: `GET /api/state/at?t=ISO` + `/api/state/diff?from=&to=` time-travel
68
+ - v0.7: SSE-стрим `/api/approvals/stream` для `idf approvals --watch`
63
69
 
64
70
  ## Лицензия
65
71
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@intent-driven/runtime-local",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Standalone Fold-runtime для local quickstart: stateful Φ + agent/exec + approvals + state — то, что делает host idf/server, но как npm-пакет",
5
5
  "type": "module",
6
6
  "exports": {
@@ -13,7 +13,8 @@
13
13
  ],
14
14
  "dependencies": {
15
15
  "cors": "^2.8.5",
16
- "express": "^4.21.2"
16
+ "express": "^4.21.2",
17
+ "@intent-driven/engine": "0.4.0"
17
18
  },
18
19
  "devDependencies": {
19
20
  "vitest": "^4.1.0",
@@ -3,6 +3,20 @@ import cors from "cors";
3
3
  import { readFileSync } from "node:fs";
4
4
  import { fileURLToPath } from "node:url";
5
5
  import { dirname, join } from "node:path";
6
+ import { createEngine, createInMemoryPersistence } from "@intent-driven/engine";
7
+
8
+ /**
9
+ * Engine validator JSON-парсит ef.value если оно string (host-convention для
10
+ * SQLite JSONB columns). Seed-эффекты от user-кода обычно содержат native
11
+ * JS values — нормализуем primitive strings обратно в JSON-encoded.
12
+ */
13
+ function normalizeSeedEffect(ef) {
14
+ if (ef.value === undefined || ef.value === null) return { ...ef };
15
+ if (typeof ef.value === "string") {
16
+ return { ...ef, value: JSON.stringify(ef.value) };
17
+ }
18
+ return { ...ef };
19
+ }
6
20
 
7
21
  const PKG_VERSION = (() => {
8
22
  try {
@@ -15,7 +29,7 @@ const PKG_VERSION = (() => {
15
29
  })();
16
30
 
17
31
  export function createRuntime(opts = {}) {
18
- const { ontology, port = 3001, host = "127.0.0.1" } = opts;
32
+ const { ontology, seed = [], port = 3001, host = "127.0.0.1" } = opts;
19
33
 
20
34
  if (!ontology || typeof ontology !== "object") {
21
35
  throw new Error("createRuntime: ontology is required");
@@ -23,6 +37,12 @@ export function createRuntime(opts = {}) {
23
37
 
24
38
  const domain = ontology.name || "default";
25
39
 
40
+ const persistence = createInMemoryPersistence();
41
+ const engine = createEngine({
42
+ domain: { INTENTS: ontology.intents || {}, ONTOLOGY: ontology },
43
+ persistence,
44
+ });
45
+
26
46
  const app = express();
27
47
  app.use(cors());
28
48
  app.use(express.json({ limit: "10mb" }));
@@ -61,17 +81,52 @@ export function createRuntime(opts = {}) {
61
81
  });
62
82
  });
63
83
 
84
+ app.get("/api/state/current", async (_req, res, next) => {
85
+ try {
86
+ const world = await engine.foldWorld();
87
+ res.json({ domain, world });
88
+ } catch (err) {
89
+ next(err);
90
+ }
91
+ });
92
+
93
+ app.get("/api/agent/:domain/world", async (req, res, next) => {
94
+ if (req.params.domain !== domain) {
95
+ return res.status(503).json({
96
+ error: "ontology_unavailable",
97
+ domain: req.params.domain,
98
+ message: `Ontology for '${req.params.domain}' is not registered. Runtime serves '${domain}'.`,
99
+ });
100
+ }
101
+ try {
102
+ const world = await engine.foldWorld();
103
+ res.json({ domain, world });
104
+ } catch (err) {
105
+ next(err);
106
+ }
107
+ });
108
+
64
109
  let server = null;
65
110
 
111
+ async function loadSeed() {
112
+ if (!Array.isArray(seed) || seed.length === 0) return;
113
+ for (const ef of seed) {
114
+ await persistence.appendEffect(normalizeSeedEffect(ef));
115
+ }
116
+ }
117
+
66
118
  return {
67
119
  app,
68
120
  ontology,
69
121
  domain,
122
+ engine,
123
+ persistence,
70
124
 
71
125
  async start() {
72
126
  if (server) {
73
127
  throw new Error("runtime already started");
74
128
  }
129
+ await loadSeed();
75
130
  return await new Promise((resolve, reject) => {
76
131
  const s = app.listen(port, host, () => {
77
132
  server = s;