@roj-ai/kompjutr-platform 0.0.0 → 0.2.11

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 (57) hide show
  1. package/README.md +114 -1
  2. package/dist/event-store.d.ts +48 -0
  3. package/dist/event-store.d.ts.map +1 -0
  4. package/dist/event-store.js +206 -0
  5. package/dist/event-store.js.map +1 -0
  6. package/dist/fs.d.ts +19 -0
  7. package/dist/fs.d.ts.map +1 -0
  8. package/dist/fs.js +200 -0
  9. package/dist/fs.js.map +1 -0
  10. package/dist/git.d.ts +12 -0
  11. package/dist/git.d.ts.map +1 -0
  12. package/dist/git.js +70 -0
  13. package/dist/git.js.map +1 -0
  14. package/dist/index.d.ts +36 -0
  15. package/dist/index.d.ts.map +1 -0
  16. package/dist/index.js +57 -0
  17. package/dist/index.js.map +1 -0
  18. package/dist/llm-call-log.d.ts +23 -0
  19. package/dist/llm-call-log.d.ts.map +1 -0
  20. package/dist/llm-call-log.js +135 -0
  21. package/dist/llm-call-log.js.map +1 -0
  22. package/dist/llm-call-payloads.d.ts +12 -0
  23. package/dist/llm-call-payloads.d.ts.map +1 -0
  24. package/dist/llm-call-payloads.js +77 -0
  25. package/dist/llm-call-payloads.js.map +1 -0
  26. package/dist/process.d.ts +14 -0
  27. package/dist/process.d.ts.map +1 -0
  28. package/dist/process.js +25 -0
  29. package/dist/process.js.map +1 -0
  30. package/dist/session-log.d.ts +22 -0
  31. package/dist/session-log.d.ts.map +1 -0
  32. package/dist/session-log.js +65 -0
  33. package/dist/session-log.js.map +1 -0
  34. package/dist/shell.d.ts +11 -0
  35. package/dist/shell.d.ts.map +1 -0
  36. package/dist/shell.js +36 -0
  37. package/dist/shell.js.map +1 -0
  38. package/dist/testing/index.d.ts +2 -0
  39. package/dist/testing/index.d.ts.map +1 -0
  40. package/dist/testing/index.js +2 -0
  41. package/dist/testing/index.js.map +1 -0
  42. package/dist/testing/storage.d.ts +29 -0
  43. package/dist/testing/storage.d.ts.map +1 -0
  44. package/dist/testing/storage.js +132 -0
  45. package/dist/testing/storage.js.map +1 -0
  46. package/package.json +44 -4
  47. package/src/event-store.ts +250 -0
  48. package/src/fs.ts +232 -0
  49. package/src/git.ts +74 -0
  50. package/src/index.ts +83 -0
  51. package/src/llm-call-log.ts +204 -0
  52. package/src/llm-call-payloads.ts +95 -0
  53. package/src/process.ts +28 -0
  54. package/src/session-log.ts +79 -0
  55. package/src/shell.ts +49 -0
  56. package/src/testing/index.ts +1 -0
  57. package/src/testing/storage.ts +141 -0
package/README.md CHANGED
@@ -1,3 +1,116 @@
1
1
  # @roj-ai/kompjutr-platform
2
2
 
3
- Placeholder. Use a version >= 0.2.10.
3
+ Run the Roj SDK against a [Kompjutr](https://www.npmjs.com/package/@kompjutr/do)
4
+ workspace. This adapter uses `@kompjutr/do` **0.1.2** from npm.
5
+
6
+ ```bash
7
+ bun add @roj-ai/sdk @roj-ai/kompjutr-platform @kompjutr/do@0.1.2
8
+ ```
9
+
10
+ ## Connect a workspace
11
+
12
+ The host creates a `Workspace` with SQL storage and optional Git support, then
13
+ passes the platform and event store to the SDK:
14
+
15
+ ```ts
16
+ import { Workspace, createGit } from '@kompjutr/do'
17
+ import { bootstrap } from '@roj-ai/sdk'
18
+ import { createKompjutrPlatform, KompjutrEventStore } from '@roj-ai/kompjutr-platform'
19
+
20
+ const workspace = new Workspace({
21
+ storage, // Durable Object storage supplied by the host
22
+ git: createGit(),
23
+ defaultGitIdentity: { name: 'Agent', email: 'agent@example.com' },
24
+ })
25
+ workspace.filesystem.mkdir('/data', { recursive: true })
26
+ workspace.filesystem.mkdir('/workspace', { recursive: true })
27
+
28
+ const platform = createKompjutrPlatform(workspace, { scheduler })
29
+ const eventStore = new KompjutrEventStore(workspace.db)
30
+ const services = bootstrap(config, { presets }, platform, {
31
+ eventStore,
32
+ pluginProfile: 'isolate',
33
+ })
34
+ ```
35
+
36
+ `config`, `presets`, `storage` and `scheduler` come from the host application.
37
+ Use virtual paths such as `/data` and `/workspace` in the configuration and
38
+ presets. In Durable Objects, supply an alarm-backed scheduler if work must
39
+ resume after eviction; the default scheduler uses in-memory timers.
40
+
41
+ ## Capabilities and boundaries
42
+
43
+ - Filesystem operations use the workspace's SQLite data. The optional `fsRevision`
44
+ port is omitted because upstream revisions do not cover Git-only changes.
45
+ - Git status and shell Git commands require `git: createGit()` on the workspace.
46
+ - The shell interprets supported commands in the virtual filesystem. It is not
47
+ a native process runner. Process spawning and native dev servers are unsupported;
48
+ hosts can supply a service executor through SDK bootstrap overrides.
49
+ - `shellConfinement` defaults to `none`. Set it to `host` only when the host
50
+ isolates the entire workspace. It does not create a per-directory sandbox.
51
+ Shell path grants are unsupported.
52
+ - Known limitation: the host shell runner does not implement the virtual
53
+ `/home/user/session` and `/home/user/workspace` namespace exposed by
54
+ preset-level `sandboxed: true`. Support requires a native upstream namespace
55
+ shared by filesystem resolution, shell execution and Git; this work is deferred.
56
+ Existing Bun/bwrap virtual paths are unchanged.
57
+ - Session logs, LLM call logs and events are stored as SQL rows. Pass the
58
+ `KompjutrEventStore` explicitly to bootstrap to use SQL event persistence.
59
+ - The host owns authentication, workspace isolation and storage lifetime.
60
+
61
+ ## LLM call storage
62
+
63
+ `KompjutrLLMCallLog` stores the exact request, response, metrics and error
64
+ strings in fixed **256 KiB byte blocks**. Agent IDs, model names and provider
65
+ request IDs use the same storage, so large scalar values cannot inflate the
66
+ parent row. Each field has explicit presence, byte-length and block-count
67
+ metadata; blocks have a field name and sequence number. Strings are JSON-string
68
+ encoded before UTF-8 encoding to preserve Unicode, including lone surrogates.
69
+ Reads assemble and decode blocks in JavaScript, without SQL concatenation.
70
+ Missing blocks, invalid metadata and malformed encodings fail visibly.
71
+
72
+ The parent keeps only session/call identity keys, timestamps, status and a
73
+ format version. With the SDK's bounded identity keys (UUIDv7 call IDs), parent,
74
+ metadata and block rows stay well below the documented
75
+ [2 MB SQLite row/string/BLOB limit](https://developers.cloudflare.com/durable-objects/platform/limits/).
76
+ Local tests measure actual stored values and returned SQL rows
77
+ against a conservative 2,000,000-byte budget; they do not prove Cloudflare's
78
+ exact engine limit.
79
+
80
+ Create, complete, retention and delete run in synchronous SQL transactions.
81
+ Deleting a parent also deletes its field metadata and blocks. Completion of a
82
+ reaped call is a no-op. Normal completion replaces only outcome fields and
83
+ does not read or rewrite request blocks. Retention defaults to the newest
84
+ 200 calls per session; `maxCallsPerSession: 0` retains every call.
85
+
86
+ `maxBlobBytes` defaults to `undefined`, so the SDK caller does not clamp the
87
+ request. An explicit positive value opts into the existing caller-side request
88
+ clamp, measured in UTF-8 bytes. Zero or a negative value disables it. This option
89
+ does not set the block size, limit other fields, or truncate direct store writes.
90
+ The adapter preserves the strings it receives and propagates database failures.
91
+
92
+ Existing inline rows remain readable. Their first completion migrates the
93
+ request and scalar fields transactionally; this one-time migration can read the
94
+ legacy request. An explicit format marker distinguishes inline and block rows,
95
+ including empty strings. **Older adapters cannot read the new block format.**
96
+
97
+ ## Local tests
98
+
99
+ The Bun-only `@roj-ai/kompjutr-platform/testing` export provides
100
+ `BunSqliteStorage`, a local storage adapter that enforces the Durable Object
101
+ 100-bind-parameter limit:
102
+
103
+ ```ts
104
+ import { BunSqliteStorage } from '@roj-ai/kompjutr-platform/testing'
105
+
106
+ const storage = new BunSqliteStorage(':memory:')
107
+ // Pass storage to Workspace, then close it when the host shuts down.
108
+ storage.close()
109
+ ```
110
+
111
+ Run the adapter tests from the repository root:
112
+
113
+ ```bash
114
+ bun test packages/kompjutr-platform/src
115
+ bunx tsc -p packages/kompjutr-platform/tsconfig.test.json --noEmit
116
+ ```
@@ -0,0 +1,48 @@
1
+ /**
2
+ * `EventStore` over the workspace's own SQLite rather than a JSONL file in it.
3
+ *
4
+ * The file store rewrites a blob through the filesystem on every append and
5
+ * parses the whole file on every load. One row per event turns an append into
6
+ * an insert and `loadRange` into a seek down the clustered `(session_id, seq)`
7
+ * key. This is the write path an event-sourced session spends its day on.
8
+ */
9
+ import { BaseEventStore, SessionId } from '@roj-ai/sdk';
10
+ import type { DomainEvent, LoadRangeOptions, LoadRangeResult, Logger, SessionMetadata } from '@roj-ai/sdk';
11
+ import type { SqlDatabase } from '@kompjutr/do';
12
+ export declare class KompjutrEventStore extends BaseEventStore {
13
+ #private;
14
+ private readonly db;
15
+ private readonly logger?;
16
+ constructor(db: SqlDatabase, logger?: Logger | undefined);
17
+ protected doAppend(sessionId: SessionId, event: DomainEvent): Promise<void>;
18
+ protected doAppendBatch(sessionId: SessionId, events: DomainEvent[]): Promise<void>;
19
+ load(sessionId: SessionId): Promise<DomainEvent[]>;
20
+ loadRange(sessionId: SessionId, options?: LoadRangeOptions): Promise<LoadRangeResult>;
21
+ exists(sessionId: SessionId): Promise<boolean>;
22
+ /**
23
+ * Every session this store holds, from either side of the pair.
24
+ *
25
+ * Neither side implies the other. A session created by `updateMetadata` alone
26
+ * has no events yet and the file store still lists it; a session whose
27
+ * metadata write failed after its rows landed has events and no metadata, and
28
+ * then the log is the only record it ran. So the answer is a union — taken
29
+ * without reading the log, because a status poll counts these.
30
+ *
31
+ * The recursive term seeks from one distinct id straight to the next down the
32
+ * `(session_id, seq)` primary key, so the cost follows sessions rather than
33
+ * events. `SELECT DISTINCT session_id` reads every row instead.
34
+ */
35
+ listSessions(): Promise<SessionId[]>;
36
+ /**
37
+ * Drop every row this store holds for one session, and report how many events went.
38
+ *
39
+ * Deliberately outside the `EventStore` contract: the log is the only record
40
+ * that a session existed, so nothing in the SDK can reach this by accident.
41
+ * Whether a session may be dropped is the caller's policy.
42
+ */
43
+ deleteSession(sessionId: SessionId): Promise<number>;
44
+ protected readMetadata(sessionId: SessionId): Promise<SessionMetadata | null>;
45
+ protected writeMetadata(sessionId: SessionId, metadata: SessionMetadata): Promise<void>;
46
+ protected getAllSessionMetadata(): Promise<SessionMetadata[]>;
47
+ }
48
+ //# sourceMappingURL=event-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"event-store.d.ts","sourceRoot":"","sources":["../src/event-store.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,cAAc,EAAuE,SAAS,EAAyB,MAAM,aAAa,CAAA;AACnJ,OAAO,KAAK,EAAE,WAAW,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAC1G,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAc/C,qBAAa,kBAAmB,SAAQ,cAAc;;IAKpD,OAAO,CAAC,QAAQ,CAAC,EAAE;IACnB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC;gBADP,EAAE,EAAE,WAAW,EACf,MAAM,CAAC,EAAE,MAAM,YAAA;cAsBjB,QAAQ,CAAC,SAAS,EAAE,SAAS,EAAE,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;cAIjE,aAAa,CAAC,SAAS,EAAE,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAyBnF,IAAI,CAAC,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IAKlD,SAAS,CAAC,SAAS,EAAE,SAAS,EAAE,OAAO,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,eAAe,CAAC;IA0BrF,MAAM,CAAC,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,OAAO,CAAC;IAIpD;;;;;;;;;;;;OAYG;IACG,YAAY,IAAI,OAAO,CAAC,SAAS,EAAE,CAAC;IAkB1C;;;;;;OAMG;IACG,aAAa,CAAC,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC;cAgB1C,YAAY,CAAC,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC;cAcnE,aAAa,CAAC,SAAS,EAAE,SAAS,EAAE,QAAQ,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC;cAS7E,qBAAqB,IAAI,OAAO,CAAC,eAAe,EAAE,CAAC;CAuDnE"}
@@ -0,0 +1,206 @@
1
+ /**
2
+ * `EventStore` over the workspace's own SQLite rather than a JSONL file in it.
3
+ *
4
+ * The file store rewrites a blob through the filesystem on every append and
5
+ * parses the whole file on every load. One row per event turns an append into
6
+ * an insert and `loadRange` into a seek down the clustered `(session_id, seq)`
7
+ * key. This is the write path an event-sourced session spends its day on.
8
+ */
9
+ import { BaseEventStore, domainEventSchema, EventAppendError, EventStoreError, isDomainEvent, SessionId, sessionMetadataSchema } from '@roj-ai/sdk';
10
+ const EVENTS_TABLE = 'roj_events';
11
+ const METADATA_TABLE = 'roj_session_metadata';
12
+ /** A Durable Object binds at most 100 parameters per statement, and a row costs three. */
13
+ const EVENTS_PER_INSERT = 33;
14
+ export class KompjutrEventStore extends BaseEventStore {
15
+ db;
16
+ logger;
17
+ /** Tail of the append chain per session; see #serialized. */
18
+ #appendTails = new Map();
19
+ constructor(db, logger) {
20
+ super();
21
+ this.db = db;
22
+ this.logger = logger;
23
+ // WITHOUT ROWID stores rows in primary-key order, so a session's events are
24
+ // contiguous and loadRange never leaves the index.
25
+ this.db.run(`CREATE TABLE IF NOT EXISTS ${EVENTS_TABLE} (
26
+ session_id TEXT NOT NULL,
27
+ seq INTEGER NOT NULL,
28
+ payload TEXT NOT NULL,
29
+ PRIMARY KEY (session_id, seq)
30
+ ) WITHOUT ROWID`);
31
+ this.db.run(`CREATE TABLE IF NOT EXISTS ${METADATA_TABLE} (
32
+ session_id TEXT PRIMARY KEY,
33
+ metadata TEXT NOT NULL
34
+ )`);
35
+ }
36
+ async doAppend(sessionId, event) {
37
+ await this.doAppendBatch(sessionId, [event]);
38
+ }
39
+ async doAppendBatch(sessionId, events) {
40
+ if (events.length === 0)
41
+ return;
42
+ await this.#serialized(sessionId, async () => {
43
+ try {
44
+ // One transaction, so a batch too wide for a single statement still
45
+ // lands whole. The old shape relied on the host committing a
46
+ // synchronous run together, which only a Durable Object promises.
47
+ this.db.transactionSync(() => {
48
+ const firstSeq = this.#lastSeq(sessionId) + 1;
49
+ for (let offset = 0; offset < events.length; offset += EVENTS_PER_INSERT) {
50
+ const chunk = events.slice(offset, offset + EVENTS_PER_INSERT);
51
+ const tuples = chunk.map(() => '(?, ?, ?)').join(', ');
52
+ const bindings = chunk.flatMap((event, index) => [sessionId, firstSeq + offset + index, JSON.stringify(event)]);
53
+ this.db.run(`INSERT INTO ${EVENTS_TABLE} (session_id, seq, payload) VALUES ${tuples}`, ...bindings);
54
+ }
55
+ });
56
+ await this.updateMetadataFromEvents(sessionId, events);
57
+ }
58
+ catch (error) {
59
+ throw new EventAppendError(sessionId, error);
60
+ }
61
+ });
62
+ }
63
+ async load(sessionId) {
64
+ const rows = this.db.all(`SELECT seq, payload FROM ${EVENTS_TABLE} WHERE session_id = ? ORDER BY seq`, sessionId);
65
+ return rows.map((row) => this.#decodeEvent(sessionId, row));
66
+ }
67
+ async loadRange(sessionId, options) {
68
+ const since = options?.since ?? -1;
69
+ const limit = options?.limit;
70
+ const rows = this.db.all(`SELECT seq, payload FROM ${EVENTS_TABLE} WHERE session_id = ? AND seq > ? ORDER BY seq LIMIT ?`, sessionId, since,
71
+ // SQLite reads a negative limit as unlimited; clamp rather than inherit that.
72
+ limit === undefined ? -1 : Math.max(limit, 0));
73
+ const first = rows[0];
74
+ const last = rows[rows.length - 1];
75
+ if (first === undefined || last === undefined) {
76
+ // Report the store's last index instead, so a poller keeps its cursor.
77
+ return { events: [], fromIndex: -1, toIndex: this.#lastSeq(sessionId) };
78
+ }
79
+ return {
80
+ events: rows.map((row) => this.#decodeEvent(sessionId, row)),
81
+ fromIndex: first.seq,
82
+ toIndex: last.seq,
83
+ };
84
+ }
85
+ async exists(sessionId) {
86
+ return this.db.one(`SELECT 1 FROM ${EVENTS_TABLE} WHERE session_id = ? LIMIT 1`, sessionId) !== undefined;
87
+ }
88
+ /**
89
+ * Every session this store holds, from either side of the pair.
90
+ *
91
+ * Neither side implies the other. A session created by `updateMetadata` alone
92
+ * has no events yet and the file store still lists it; a session whose
93
+ * metadata write failed after its rows landed has events and no metadata, and
94
+ * then the log is the only record it ran. So the answer is a union — taken
95
+ * without reading the log, because a status poll counts these.
96
+ *
97
+ * The recursive term seeks from one distinct id straight to the next down the
98
+ * `(session_id, seq)` primary key, so the cost follows sessions rather than
99
+ * events. `SELECT DISTINCT session_id` reads every row instead.
100
+ */
101
+ async listSessions() {
102
+ const rows = this.db.all(`WITH RECURSIVE event_sessions(session_id) AS (
103
+ SELECT MIN(session_id) FROM ${EVENTS_TABLE}
104
+ UNION ALL
105
+ SELECT (SELECT MIN(session_id) FROM ${EVENTS_TABLE} WHERE session_id > previous.session_id)
106
+ FROM event_sessions AS previous
107
+ WHERE previous.session_id IS NOT NULL
108
+ )
109
+ SELECT session_id FROM event_sessions WHERE session_id IS NOT NULL
110
+ UNION
111
+ SELECT session_id FROM ${METADATA_TABLE}
112
+ ORDER BY session_id`);
113
+ return rows.map((row) => SessionId(row.session_id));
114
+ }
115
+ /**
116
+ * Drop every row this store holds for one session, and report how many events went.
117
+ *
118
+ * Deliberately outside the `EventStore` contract: the log is the only record
119
+ * that a session existed, so nothing in the SDK can reach this by accident.
120
+ * Whether a session may be dropped is the caller's policy.
121
+ */
122
+ async deleteSession(sessionId) {
123
+ let events = 0;
124
+ // Serialized with appends, so a delete cannot land between a batch's
125
+ // sequence claim and its inserts and leave the tail of that batch behind.
126
+ await this.#serialized(sessionId, async () => {
127
+ this.db.transactionSync(() => {
128
+ events = this.db.scalar(`SELECT COUNT(*) FROM ${EVENTS_TABLE} WHERE session_id = ?`, sessionId) ?? 0;
129
+ this.db.run(`DELETE FROM ${EVENTS_TABLE} WHERE session_id = ?`, sessionId);
130
+ this.db.run(`DELETE FROM ${METADATA_TABLE} WHERE session_id = ?`, sessionId);
131
+ });
132
+ });
133
+ return events;
134
+ }
135
+ async readMetadata(sessionId) {
136
+ const row = this.db.one(`SELECT metadata FROM ${METADATA_TABLE} WHERE session_id = ?`, sessionId);
137
+ return row === undefined ? null : this.#decodeMetadata(sessionId, row.metadata);
138
+ }
139
+ /** A session whose metadata does not parse is skipped, never thrown over. */
140
+ #skip(sessionId, reason) {
141
+ this.logger?.warn('Skipping invalid session metadata', {
142
+ sessionId,
143
+ reason,
144
+ });
145
+ return null;
146
+ }
147
+ async writeMetadata(sessionId, metadata) {
148
+ this.db.run(`INSERT INTO ${METADATA_TABLE} (session_id, metadata) VALUES (?, ?)
149
+ ON CONFLICT(session_id) DO UPDATE SET metadata = excluded.metadata`, sessionId, JSON.stringify(metadata));
150
+ }
151
+ async getAllSessionMetadata() {
152
+ const rows = this.db.all(`SELECT session_id, metadata FROM ${METADATA_TABLE} ORDER BY session_id`);
153
+ return rows.map((row) => this.#decodeMetadata(SessionId(row.session_id), row.metadata)).filter((metadata) => metadata !== null);
154
+ }
155
+ /** Index of the last stored event, -1 when the session has none. */
156
+ #lastSeq(sessionId) {
157
+ return this.db.scalar(`SELECT MAX(seq) FROM ${EVENTS_TABLE} WHERE session_id = ?`, sessionId) ?? -1;
158
+ }
159
+ /**
160
+ * Run appends for one session one at a time.
161
+ *
162
+ * The SQL is synchronous but the metadata update that follows it is not, so
163
+ * concurrent appends would otherwise read-modify-write the same metrics.
164
+ */
165
+ #serialized(sessionId, task) {
166
+ const previous = this.#appendTails.get(sessionId) ?? Promise.resolve();
167
+ const next = previous.then(task);
168
+ // The stored tail must never reject, or the next append inherits this failure.
169
+ const tail = next
170
+ .catch(() => undefined)
171
+ .then(() => {
172
+ if (this.#appendTails.get(sessionId) === tail)
173
+ this.#appendTails.delete(sessionId);
174
+ });
175
+ this.#appendTails.set(sessionId, tail);
176
+ return next;
177
+ }
178
+ #decodeEvent(sessionId, row) {
179
+ try {
180
+ const parsed = domainEventSchema.parse(JSON.parse(row.payload));
181
+ // The schema validates fields; the SDK guard supplies the domain event brand.
182
+ if (!isDomainEvent(parsed))
183
+ throw new Error('Invalid domain event');
184
+ return parsed;
185
+ }
186
+ catch (error) {
187
+ throw new EventStoreError(`Failed to parse event at index ${row.seq}`, sessionId, error);
188
+ }
189
+ }
190
+ /**
191
+ * Null rather than a throw, matching `FileEventStore`: one unreadable row must
192
+ * not take out the listing every other session appears in.
193
+ */
194
+ #decodeMetadata(sessionId, raw) {
195
+ let parsed;
196
+ try {
197
+ parsed = JSON.parse(raw);
198
+ }
199
+ catch (error) {
200
+ return this.#skip(sessionId, String(error));
201
+ }
202
+ const result = sessionMetadataSchema.safeParse(parsed);
203
+ return result.success ? result.data : this.#skip(sessionId, result.error.message);
204
+ }
205
+ }
206
+ //# sourceMappingURL=event-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"event-store.js","sourceRoot":"","sources":["../src/event-store.ts"],"names":[],"mappings":"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"}
package/dist/fs.d.ts ADDED
@@ -0,0 +1,19 @@
1
+ /**
2
+ * `FileSystem` port over a kompjutr workspace.
3
+ *
4
+ * kompjutr ships a node:fs-shaped shim (`NodeFsCompat`) over its own bulk-first
5
+ * `Filesystem`, and the shim already answers the port's optional verbs in the
6
+ * shape the port asks for. So most of this file forwards. What it does not
7
+ * forward is what the shim does not do, or does with different semantics than
8
+ * the port states — `appendFile`, a recursive `cp`, and a walk's symlink sizes.
9
+ */
10
+ import type { Filesystem, NodeFsCompat } from '@kompjutr/do';
11
+ import type { FileSystem } from '@roj-ai/sdk/platform';
12
+ export interface KompjutrFileSystemOptions {
13
+ /** The node:fs-shaped shim. `Workspace.fs`. */
14
+ compat: NodeFsCompat;
15
+ /** The bulk API underneath it, over the same database. `Workspace.filesystem`. */
16
+ filesystem: Filesystem;
17
+ }
18
+ export declare function createKompjutrFileSystem(options: KompjutrFileSystemOptions): FileSystem;
19
+ //# sourceMappingURL=fs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fs.d.ts","sourceRoot":"","sources":["../src/fs.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAQ,MAAM,cAAc,CAAA;AAClE,OAAO,KAAK,EAAE,UAAU,EAAkG,MAAM,sBAAsB,CAAA;AAQtJ,MAAM,WAAW,yBAAyB;IACzC,+CAA+C;IAC/C,MAAM,EAAE,YAAY,CAAA;IACpB,kFAAkF;IAClF,UAAU,EAAE,UAAU,CAAA;CACtB;AAgBD,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,yBAAyB,GAAG,UAAU,CA+LvF"}
package/dist/fs.js ADDED
@@ -0,0 +1,200 @@
1
+ /**
2
+ * `FileSystem` port over a kompjutr workspace.
3
+ *
4
+ * kompjutr ships a node:fs-shaped shim (`NodeFsCompat`) over its own bulk-first
5
+ * `Filesystem`, and the shim already answers the port's optional verbs in the
6
+ * shape the port asks for. So most of this file forwards. What it does not
7
+ * forward is what the shim does not do, or does with different semantics than
8
+ * the port states — `appendFile`, a recursive `cp`, and a walk's symlink sizes.
9
+ */
10
+ /** Pages a subtree scan. kompjutr caps a page at 1,000 rows. */
11
+ const SCAN_PAGE = 1_000;
12
+ /** What `Filesystem.copyFiles` accepts in one call. */
13
+ const COPY_BATCH = 1_000;
14
+ function toBytes(data) {
15
+ return typeof data === 'string' ? new TextEncoder().encode(data) : data;
16
+ }
17
+ function fileError(code, path, message) {
18
+ return Object.assign(new Error(`${message}: ${path}`), { code, path });
19
+ }
20
+ /** The port's `type`, which names a directory in full and admits things kompjutr cannot produce. */
21
+ function walkType(type) {
22
+ if (type === 'dir')
23
+ return 'directory';
24
+ return type;
25
+ }
26
+ export function createKompjutrFileSystem(options) {
27
+ const { compat, filesystem } = options;
28
+ /** Every path under `root`, parents before children. Excludes `root` itself. */
29
+ const scanSubtree = (root) => {
30
+ const paths = [];
31
+ let after;
32
+ for (;;) {
33
+ const page = filesystem.scan(root, { after, limit: SCAN_PAGE });
34
+ if (page.length === 0)
35
+ return paths;
36
+ for (const entry of page)
37
+ paths.push(entry.path);
38
+ if (page.length < SCAN_PAGE)
39
+ return paths;
40
+ after = page[page.length - 1]?.path;
41
+ }
42
+ };
43
+ /** Copies inside SQLite, so file bytes never enter the isolate. */
44
+ const copyEntries = (entries, force = true) => {
45
+ let pending = entries.filter(({ source, destination }) => {
46
+ const sourceStat = filesystem.stat(source);
47
+ const destinationStat = filesystem.stat(destination);
48
+ if (sourceStat !== null && destinationStat !== null && sourceStat.ino === destinationStat.ino) {
49
+ throw fileError('ERR_FS_CP_EINVAL', destination, 'source and destination are the same file');
50
+ }
51
+ if (sourceStat?.type === 'dir' && destinationStat !== null && destinationStat.type !== 'dir') {
52
+ throw fileError('ERR_FS_CP_DIR_TO_NON_DIR', destination, 'cannot overwrite a non-directory with a directory');
53
+ }
54
+ if (sourceStat?.type !== 'dir' && destinationStat?.type === 'dir') {
55
+ throw fileError('ERR_FS_CP_NON_DIR_TO_DIR', destination, 'cannot overwrite a directory with a non-directory');
56
+ }
57
+ return force || destinationStat === null || sourceStat?.type === 'dir';
58
+ });
59
+ while (pending.length > 0) {
60
+ const batch = filesystem.copyFiles(pending.slice(0, COPY_BATCH), {
61
+ parents: true,
62
+ });
63
+ const deferred = [...batch.remaining, ...pending.slice(COPY_BATCH)];
64
+ if (deferred.length >= pending.length)
65
+ throw fileError('EIO', pending[0]?.source ?? '', 'copy made no progress');
66
+ pending = deferred;
67
+ }
68
+ };
69
+ const remove = (path, rmOptions) => {
70
+ if (!rmOptions?.recursive && filesystem.stat(path)?.type === 'dir') {
71
+ throw fileError('ERR_FS_EISDIR', path, 'is a directory');
72
+ }
73
+ compat.rmSync(path, rmOptions);
74
+ };
75
+ return {
76
+ // Every method here is `async`, and deliberately so: kompjutr's shim reaches
77
+ // its synchronous core through `Promise.resolve(xSync(...))`, so a failure
78
+ // throws where the port says it rejects. `async` puts that back.
79
+ readFile: (async (path, encoding) => encoding ? compat.readFileSync(path, { encoding }) : compat.readFileSync(path)),
80
+ writeFile: async (path, data) => {
81
+ compat.writeFileSync(path, data);
82
+ },
83
+ // The shim's own appendFile is ENOSYS; write at the current end instead.
84
+ appendFile: async (path, data) => {
85
+ const stat = filesystem.statTarget(path);
86
+ if (stat === null) {
87
+ compat.writeFileSync(path, data);
88
+ return;
89
+ }
90
+ filesystem.writeRange(path, toBytes(data), stat.size);
91
+ },
92
+ mkdir: async (path, mkdirOptions) => {
93
+ compat.mkdirSync(path, mkdirOptions);
94
+ },
95
+ readdir: (async (path, readdirOptions) => readdirOptions?.withFileTypes ? compat.readdirSync(path, { withFileTypes: true }) : compat.readdirSync(path)),
96
+ stat: async (path) => compat.statSync(path),
97
+ lstat: async (path) => compat.lstatSync(path),
98
+ access: async (path, mode) => {
99
+ compat.accessSync(path, mode);
100
+ },
101
+ unlink: async (path) => {
102
+ compat.unlinkSync(path);
103
+ },
104
+ rm: async (path, rmOptions) => {
105
+ remove(path, rmOptions);
106
+ },
107
+ cp: async (source, dest, cpOptions) => {
108
+ const stat = filesystem.stat(source);
109
+ if (stat === null)
110
+ throw fileError('ENOENT', source, 'no such file or directory');
111
+ if (stat.type !== 'dir') {
112
+ copyEntries([{ source, destination: dest }], cpOptions?.force);
113
+ return;
114
+ }
115
+ if (!cpOptions?.recursive)
116
+ throw fileError('EISDIR', source, 'is a directory');
117
+ const root = filesystem.realpath(source);
118
+ copyEntries([
119
+ { source: root, destination: dest },
120
+ ...scanSubtree(root).map((path) => ({
121
+ source: path,
122
+ destination: `${dest}${path.slice(root.length)}`,
123
+ })),
124
+ ], cpOptions?.force);
125
+ },
126
+ // The shim exposes fds, not handles; wrap one in the read subset the port uses.
127
+ open: async (path, flags) => {
128
+ const fd = compat.openSync(path, flags ?? 'r');
129
+ return {
130
+ stat: async () => compat.fstatSync(fd),
131
+ read: async (buffer, offset, length, position) => ({
132
+ bytesRead: compat.readSync(fd, buffer, offset, length, position),
133
+ buffer,
134
+ }),
135
+ close: async () => {
136
+ compat.closeSync(fd);
137
+ },
138
+ };
139
+ },
140
+ exists: async (path) => compat.existsSync(path),
141
+ realpath: async (path) => compat.realpathSync(path),
142
+ walk: async (dir, walkOptions) => {
143
+ const entries = await compat.walk(dir, walkOptions);
144
+ return entries.map((entry) => ({
145
+ path: entry.path,
146
+ type: walkType(entry.type),
147
+ // The shim reports a symlink's own size; the port wants the target's,
148
+ // because the readdir-and-stat loop it replaces follows the link.
149
+ size: entry.type === 'symlink' ? (filesystem.statTarget(entry.path)?.size ?? 0) : entry.size,
150
+ mtime: entry.mtime,
151
+ }));
152
+ },
153
+ readFiles: async (paths) => {
154
+ try {
155
+ const entries = await compat.readFiles(paths);
156
+ return entries.map((entry, index) => ({
157
+ path: paths[index] ?? entry.path,
158
+ ...(entry.content === undefined ? {} : { content: Buffer.from(entry.content) }),
159
+ ...(entry.error === undefined ? {} : { error: entry.error }),
160
+ }));
161
+ }
162
+ catch (error) {
163
+ if (!(error instanceof Error) || !('code' in error) || (error.code !== 'ENOTDIR' && error.code !== 'ELOOP'))
164
+ throw error;
165
+ // Path resolution can abort the upstream batch before it reports individual failures.
166
+ return Promise.all(paths.map(async (path) => {
167
+ try {
168
+ const [entry] = await compat.readFiles([path]);
169
+ if (!entry)
170
+ throw new Error('readFiles returned no entry');
171
+ return {
172
+ path,
173
+ ...(entry.content === undefined ? {} : { content: Buffer.from(entry.content) }),
174
+ ...(entry.error === undefined ? {} : { error: entry.error }),
175
+ };
176
+ }
177
+ catch (entryError) {
178
+ if (entryError instanceof Error && 'code' in entryError && typeof entryError.code === 'string') {
179
+ return { path, error: entryError.code };
180
+ }
181
+ throw entryError;
182
+ }
183
+ }));
184
+ }
185
+ },
186
+ // The bulk primitive replaces final links; resolve them as single writes do.
187
+ writeFiles: async (entries, writeOptions) => {
188
+ filesystem.writeFiles(entries.map((entry) => ({
189
+ path: filesystem.realpath(entry.path),
190
+ bytes: toBytes(entry.content),
191
+ })), { parents: writeOptions?.createParents ?? false });
192
+ },
193
+ rmFiles: async (paths, rmOptions) => {
194
+ for (const path of paths)
195
+ remove(path, rmOptions);
196
+ },
197
+ scopeReads: (fn) => filesystem.withReadScope(fn),
198
+ };
199
+ }
200
+ //# sourceMappingURL=fs.js.map
package/dist/fs.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fs.js","sourceRoot":"","sources":["../src/fs.ts"],"names":[],"mappings":"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"}
package/dist/git.d.ts ADDED
@@ -0,0 +1,12 @@
1
+ /**
2
+ * `GitClient` port over kompjutr's git runtime.
3
+ *
4
+ * The port names four questions `git-status` asks, and kompjutr answers each
5
+ * one directly rather than by walking a tree: status reads its index tracker,
6
+ * and `divergence` counts both sides of a fork in one call. Two of the four had
7
+ * no honest answer over the binding this replaces.
8
+ */
9
+ import type { GitClient } from '@roj-ai/sdk/platform';
10
+ import type { Git } from '@kompjutr/do';
11
+ export declare function createKompjutrGitClient(git: Git): GitClient;
12
+ //# sourceMappingURL=git.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git.d.ts","sourceRoot":"","sources":["../src/git.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAkF,MAAM,sBAAsB,CAAA;AACrI,OAAO,KAAK,EAAE,GAAG,EAAe,MAAM,cAAc,CAAA;AAcpD,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,GAAG,GAAG,SAAS,CAiD3D"}