@roj-ai/kompjutr-platform 0.0.0 → 0.2.10
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 +114 -1
- package/dist/event-store.d.ts +48 -0
- package/dist/event-store.d.ts.map +1 -0
- package/dist/event-store.js +206 -0
- package/dist/event-store.js.map +1 -0
- package/dist/fs.d.ts +19 -0
- package/dist/fs.d.ts.map +1 -0
- package/dist/fs.js +200 -0
- package/dist/fs.js.map +1 -0
- package/dist/git.d.ts +12 -0
- package/dist/git.d.ts.map +1 -0
- package/dist/git.js +70 -0
- package/dist/git.js.map +1 -0
- package/dist/index.d.ts +36 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +57 -0
- package/dist/index.js.map +1 -0
- package/dist/llm-call-log.d.ts +23 -0
- package/dist/llm-call-log.d.ts.map +1 -0
- package/dist/llm-call-log.js +135 -0
- package/dist/llm-call-log.js.map +1 -0
- package/dist/llm-call-payloads.d.ts +12 -0
- package/dist/llm-call-payloads.d.ts.map +1 -0
- package/dist/llm-call-payloads.js +77 -0
- package/dist/llm-call-payloads.js.map +1 -0
- package/dist/process.d.ts +14 -0
- package/dist/process.d.ts.map +1 -0
- package/dist/process.js +25 -0
- package/dist/process.js.map +1 -0
- package/dist/session-log.d.ts +22 -0
- package/dist/session-log.d.ts.map +1 -0
- package/dist/session-log.js +65 -0
- package/dist/session-log.js.map +1 -0
- package/dist/shell.d.ts +11 -0
- package/dist/shell.d.ts.map +1 -0
- package/dist/shell.js +36 -0
- package/dist/shell.js.map +1 -0
- package/dist/testing/index.d.ts +2 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +2 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/testing/storage.d.ts +29 -0
- package/dist/testing/storage.d.ts.map +1 -0
- package/dist/testing/storage.js +132 -0
- package/dist/testing/storage.js.map +1 -0
- package/package.json +44 -4
- package/src/event-store.ts +250 -0
- package/src/fs.ts +232 -0
- package/src/git.ts +74 -0
- package/src/index.ts +83 -0
- package/src/llm-call-log.ts +204 -0
- package/src/llm-call-payloads.ts +95 -0
- package/src/process.ts +28 -0
- package/src/session-log.ts +79 -0
- package/src/shell.ts +49 -0
- package/src/testing/index.ts +1 -0
- package/src/testing/storage.ts +141 -0
package/README.md
CHANGED
|
@@ -1,3 +1,116 @@
|
|
|
1
1
|
# @roj-ai/kompjutr-platform
|
|
2
2
|
|
|
3
|
-
|
|
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
|
package/dist/fs.d.ts.map
ADDED
|
@@ -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"}
|