@corenel/harness 0.1.0 → 0.3.2
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/dist/acp/agentMap.d.ts +34 -0
- package/dist/acp/agentMap.d.ts.map +1 -0
- package/dist/acp/agentMap.js +110 -0
- package/dist/acp/agentMap.js.map +1 -0
- package/dist/acp/index.d.ts +4 -0
- package/dist/acp/index.d.ts.map +1 -0
- package/dist/acp/index.js +6 -0
- package/dist/acp/index.js.map +1 -0
- package/dist/acp/policyMap.d.ts +55 -0
- package/dist/acp/policyMap.d.ts.map +1 -0
- package/dist/acp/policyMap.js +121 -0
- package/dist/acp/policyMap.js.map +1 -0
- package/dist/acp/types.d.ts +41 -0
- package/dist/acp/types.d.ts.map +1 -0
- package/dist/acp/types.js +14 -0
- package/dist/acp/types.js.map +1 -0
- package/dist/api/backend.d.ts +39 -0
- package/dist/api/backend.d.ts.map +1 -0
- package/dist/api/backend.js +126 -0
- package/dist/api/backend.js.map +1 -0
- package/dist/api/client.d.ts +95 -0
- package/dist/api/client.d.ts.map +1 -0
- package/dist/api/client.js +212 -0
- package/dist/api/client.js.map +1 -0
- package/dist/api/fileMirrorSource.d.ts +24 -0
- package/dist/api/fileMirrorSource.d.ts.map +1 -0
- package/dist/api/fileMirrorSource.js +59 -0
- package/dist/api/fileMirrorSource.js.map +1 -0
- package/dist/api/mirror.d.ts +102 -0
- package/dist/api/mirror.d.ts.map +1 -0
- package/dist/api/mirror.js +204 -0
- package/dist/api/mirror.js.map +1 -0
- package/dist/api/mirrorScheduler.d.ts +56 -0
- package/dist/api/mirrorScheduler.d.ts.map +1 -0
- package/dist/api/mirrorScheduler.js +167 -0
- package/dist/api/mirrorScheduler.js.map +1 -0
- package/dist/conversation.d.ts +0 -11
- package/dist/conversation.d.ts.map +1 -1
- package/dist/conversation.js +1 -24
- package/dist/conversation.js.map +1 -1
- package/dist/core/budget.d.ts +40 -0
- package/dist/core/budget.d.ts.map +1 -0
- package/dist/core/budget.js +62 -0
- package/dist/core/budget.js.map +1 -0
- package/dist/core/loop.d.ts +11 -0
- package/dist/core/loop.d.ts.map +1 -1
- package/dist/core/loop.js +89 -20
- package/dist/core/loop.js.map +1 -1
- package/dist/core/pricing.d.ts +8 -0
- package/dist/core/pricing.d.ts.map +1 -0
- package/dist/core/pricing.js +12 -0
- package/dist/core/pricing.js.map +1 -0
- package/dist/core/recovery.d.ts +60 -0
- package/dist/core/recovery.d.ts.map +1 -0
- package/dist/core/recovery.js +128 -0
- package/dist/core/recovery.js.map +1 -0
- package/dist/guardrail/builtins.js +6 -6
- package/dist/guardrail/builtins.js.map +1 -1
- package/dist/guardrail/permission-service.js +1 -1
- package/dist/guardrail/permission-service.js.map +1 -1
- package/dist/guardrail/serialize.d.ts.map +1 -1
- package/dist/guardrail/serialize.js +6 -0
- package/dist/guardrail/serialize.js.map +1 -1
- package/dist/guardrail/validate.d.ts.map +1 -1
- package/dist/guardrail/validate.js +13 -0
- package/dist/guardrail/validate.js.map +1 -1
- package/dist/install/lockfile.d.ts +79 -0
- package/dist/install/lockfile.d.ts.map +1 -0
- package/dist/install/lockfile.js +155 -0
- package/dist/install/lockfile.js.map +1 -0
- package/dist/install/store.d.ts +13 -0
- package/dist/install/store.d.ts.map +1 -0
- package/dist/install/store.js +38 -0
- package/dist/install/store.js.map +1 -0
- package/dist/log.d.ts.map +1 -1
- package/dist/log.js +6 -1
- package/dist/log.js.map +1 -1
- package/dist/prompts/host.d.ts +4 -0
- package/dist/prompts/host.d.ts.map +1 -1
- package/dist/prompts/host.js.map +1 -1
- package/dist/prompts/index.d.ts +6 -0
- package/dist/prompts/index.d.ts.map +1 -1
- package/dist/prompts/index.js +16 -6
- package/dist/prompts/index.js.map +1 -1
- package/dist/prompts/library/defaults.generated.d.ts.map +1 -1
- package/dist/prompts/library/defaults.generated.js +22 -4
- package/dist/prompts/library/defaults.generated.js.map +1 -1
- package/dist/prompts/toolsMarker.d.ts +13 -0
- package/dist/prompts/toolsMarker.d.ts.map +1 -0
- package/dist/prompts/toolsMarker.js +21 -0
- package/dist/prompts/toolsMarker.js.map +1 -0
- package/dist/providers/gateway.d.ts +19 -1
- package/dist/providers/gateway.d.ts.map +1 -1
- package/dist/providers/gateway.js +33 -4
- package/dist/providers/gateway.js.map +1 -1
- package/dist/services.d.ts +13 -0
- package/dist/services.d.ts.map +1 -1
- package/dist/services.js +2 -0
- package/dist/services.js.map +1 -1
- package/dist/sessions/apiLeaseStore.d.ts +10 -0
- package/dist/sessions/apiLeaseStore.d.ts.map +1 -0
- package/dist/sessions/apiLeaseStore.js +41 -0
- package/dist/sessions/apiLeaseStore.js.map +1 -0
- package/dist/sessions/fileRecallStore.d.ts +33 -0
- package/dist/sessions/fileRecallStore.d.ts.map +1 -0
- package/dist/sessions/fileRecallStore.js +112 -0
- package/dist/sessions/fileRecallStore.js.map +1 -0
- package/dist/sessions/fileSessionStore.d.ts +20 -0
- package/dist/sessions/fileSessionStore.d.ts.map +1 -0
- package/dist/sessions/fileSessionStore.js +151 -0
- package/dist/sessions/fileSessionStore.js.map +1 -0
- package/dist/sessions/head.d.ts +90 -0
- package/dist/sessions/head.d.ts.map +1 -0
- package/dist/sessions/head.js +82 -0
- package/dist/sessions/head.js.map +1 -0
- package/dist/sessions/inflight.d.ts +58 -0
- package/dist/sessions/inflight.d.ts.map +1 -0
- package/dist/sessions/inflight.js +0 -0
- package/dist/sessions/inflight.js.map +1 -0
- package/dist/sessions/jsonl.d.ts +23 -0
- package/dist/sessions/jsonl.d.ts.map +1 -0
- package/dist/sessions/jsonl.js +52 -0
- package/dist/sessions/jsonl.js.map +1 -0
- package/dist/sessions/lease.d.ts +59 -0
- package/dist/sessions/lease.d.ts.map +1 -0
- package/dist/sessions/lease.js +115 -0
- package/dist/sessions/lease.js.map +1 -0
- package/dist/sessions/leaseStore.d.ts +46 -0
- package/dist/sessions/leaseStore.d.ts.map +1 -0
- package/dist/sessions/leaseStore.js +58 -0
- package/dist/sessions/leaseStore.js.map +1 -0
- package/dist/sessions/testFs.d.ts +5 -0
- package/dist/sessions/testFs.d.ts.map +1 -0
- package/dist/sessions/testFs.js +85 -0
- package/dist/sessions/testFs.js.map +1 -0
- package/dist/sessions/timeline.d.ts +134 -0
- package/dist/sessions/timeline.d.ts.map +1 -0
- package/dist/sessions/timeline.js +127 -0
- package/dist/sessions/timeline.js.map +1 -0
- package/dist/sidecar/host.d.ts +284 -2
- package/dist/sidecar/host.d.ts.map +1 -1
- package/dist/sidecar/host.js +725 -1
- package/dist/sidecar/host.js.map +1 -1
- package/dist/sidecar/server.d.ts +217 -2
- package/dist/sidecar/server.d.ts.map +1 -1
- package/dist/sidecar/server.js +828 -3
- package/dist/sidecar/server.js.map +1 -1
- package/dist/skills.d.ts +8 -3
- package/dist/skills.d.ts.map +1 -1
- package/dist/skills.js +71 -35
- package/dist/skills.js.map +1 -1
- package/dist/snapshot/effects.d.ts +69 -0
- package/dist/snapshot/effects.d.ts.map +1 -0
- package/dist/snapshot/effects.js +67 -0
- package/dist/snapshot/effects.js.map +1 -0
- package/dist/snapshot/manifest.d.ts +73 -0
- package/dist/snapshot/manifest.d.ts.map +1 -0
- package/dist/snapshot/manifest.js +141 -0
- package/dist/snapshot/manifest.js.map +1 -0
- package/dist/snapshot/tree.d.ts +62 -0
- package/dist/snapshot/tree.d.ts.map +1 -0
- package/dist/snapshot/tree.js +147 -0
- package/dist/snapshot/tree.js.map +1 -0
- package/dist/tools/builtins.d.ts +1 -0
- package/dist/tools/builtins.d.ts.map +1 -1
- package/dist/tools/builtins.js +150 -5
- package/dist/tools/builtins.js.map +1 -1
- package/dist/tools/files.d.ts.map +1 -1
- package/dist/tools/files.js +13 -0
- package/dist/tools/files.js.map +1 -1
- package/dist/tools/http.d.ts.map +1 -1
- package/dist/tools/http.js.map +1 -1
- package/dist/tools/namespaces.d.ts +1 -1
- package/dist/tools/namespaces.d.ts.map +1 -1
- package/dist/tools/namespaces.js +24 -0
- package/dist/tools/namespaces.js.map +1 -1
- package/dist/tools/todo.d.ts.map +1 -1
- package/dist/tools/todo.js +12 -0
- package/dist/tools/todo.js.map +1 -1
- package/package.json +26 -34
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
export interface ApiTransport {
|
|
2
|
+
/** Injected so tests need no network and node/browser differences stay out. */
|
|
3
|
+
fetch: typeof fetch;
|
|
4
|
+
/** e.g. https://api.corenel.ai/api/v1 — no trailing slash. */
|
|
5
|
+
baseUrl: string;
|
|
6
|
+
/** Bearer credential. Async because a Clerk token is minted on demand and a
|
|
7
|
+
* sandbox token may be refreshed. Return '' to send none. */
|
|
8
|
+
getToken(): Promise<string> | string;
|
|
9
|
+
}
|
|
10
|
+
export declare class ApiError extends Error {
|
|
11
|
+
readonly status: number;
|
|
12
|
+
readonly body: unknown;
|
|
13
|
+
constructor(status: number, message: string, body: unknown);
|
|
14
|
+
}
|
|
15
|
+
/** A lease call that lost. Carries the epoch that won, because "re-read and
|
|
16
|
+
* decide" is always the caller's next step and the number is what it decides on. */
|
|
17
|
+
export declare class LeaseConflict extends ApiError {
|
|
18
|
+
readonly currentEpoch: number;
|
|
19
|
+
constructor(status: number, message: string, body: unknown, currentEpoch: number);
|
|
20
|
+
}
|
|
21
|
+
export interface RemoteSession {
|
|
22
|
+
_id: string;
|
|
23
|
+
/** The id the machine running it knows it by, when it told us. */
|
|
24
|
+
localId?: string | null;
|
|
25
|
+
ownerId: string;
|
|
26
|
+
title: string;
|
|
27
|
+
status: string;
|
|
28
|
+
location: 'local' | 'hosted';
|
|
29
|
+
epoch: number;
|
|
30
|
+
holder: string | null;
|
|
31
|
+
nextIndex: number;
|
|
32
|
+
lineageRoot: string | null;
|
|
33
|
+
latestSnapshot: string | null;
|
|
34
|
+
}
|
|
35
|
+
export interface RemoteLease {
|
|
36
|
+
sessionId: string;
|
|
37
|
+
epoch: number;
|
|
38
|
+
holder: string;
|
|
39
|
+
}
|
|
40
|
+
export declare class CorenelApiClient {
|
|
41
|
+
private readonly t;
|
|
42
|
+
constructor(t: ApiTransport);
|
|
43
|
+
private request;
|
|
44
|
+
listSessions(): Promise<RemoteSession[]>;
|
|
45
|
+
createSession(input?: {
|
|
46
|
+
localId?: string;
|
|
47
|
+
title?: string;
|
|
48
|
+
location?: 'local' | 'hosted';
|
|
49
|
+
}): Promise<RemoteSession>;
|
|
50
|
+
/** Find the remote record for a session this machine knows by another name.
|
|
51
|
+
* Null when there is not one yet — which is a normal answer on first push. */
|
|
52
|
+
findByLocalId(localId: string): Promise<RemoteSession | null>;
|
|
53
|
+
getSession(id: string): Promise<RemoteSession>;
|
|
54
|
+
/**
|
|
55
|
+
* Take the write lease.
|
|
56
|
+
*
|
|
57
|
+
* `expectedEpoch` is the caller's view; the server matches and increments in
|
|
58
|
+
* one conditional update. A `LeaseConflict` means someone else holds it — the
|
|
59
|
+
* caller re-reads and decides, and must NOT retry blindly, because retrying
|
|
60
|
+
* with the winning epoch is how you take a lease from a live writer.
|
|
61
|
+
*/
|
|
62
|
+
acquireLease(sessionId: string, expectedEpoch: number, holder: string): Promise<RemoteLease>;
|
|
63
|
+
releaseLease(sessionId: string, expectedEpoch: number): Promise<number>;
|
|
64
|
+
/** Store the derived head. The epoch is mandatory: a stale writer would
|
|
65
|
+
* overwrite the real head with an older view of a timeline it never saw. */
|
|
66
|
+
putHead(sessionId: string, epoch: number, head: unknown, status?: string): Promise<void>;
|
|
67
|
+
appendTimeline(sessionId: string, epoch: number, entries: readonly unknown[]): Promise<{
|
|
68
|
+
nextIndex: number;
|
|
69
|
+
}>;
|
|
70
|
+
readTimeline(sessionId: string, since?: number): Promise<{
|
|
71
|
+
entries: unknown[];
|
|
72
|
+
nextIndex: number;
|
|
73
|
+
}>;
|
|
74
|
+
/** Which of these the store does NOT have. Batched on purpose: a capture is
|
|
75
|
+
* mostly unchanged files, and asking one at a time spends the whole capture on
|
|
76
|
+
* round trips. */
|
|
77
|
+
missingBlobs(hashes: readonly string[]): Promise<string[]>;
|
|
78
|
+
/** Upload bytes under their own hash. The server verifies before storing, so a
|
|
79
|
+
* mismatch here is a real error rather than something to paper over. */
|
|
80
|
+
putBlob(hash: string, bytes: Uint8Array): Promise<{
|
|
81
|
+
stored: boolean;
|
|
82
|
+
}>;
|
|
83
|
+
getBlob(hash: string): Promise<Uint8Array | null>;
|
|
84
|
+
putSnapshot(input: {
|
|
85
|
+
snapshotId: string;
|
|
86
|
+
sessionId: string;
|
|
87
|
+
parent?: string | null;
|
|
88
|
+
lineageRoot?: string | null;
|
|
89
|
+
manifest: unknown;
|
|
90
|
+
tree: unknown;
|
|
91
|
+
problems?: readonly string[];
|
|
92
|
+
}): Promise<unknown>;
|
|
93
|
+
getSnapshot(snapshotId: string): Promise<unknown>;
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../api/client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
/* A client for the Corenel API (api.corenel.ai).
|
|
2
|
+
*
|
|
3
|
+
* HOST-AGNOSTIC BY CONSTRUCTION. It takes a `fetch`, a base URL and a token
|
|
4
|
+
* getter, and knows nothing about browsers, Vite, Clerk or the sidecar. That is
|
|
5
|
+
* not fastidiousness: the same client has to run in the browser (Clerk bearer),
|
|
6
|
+
* in the sidecar (a user or sandbox credential) and in a hosted sandbox (its own
|
|
7
|
+
* scoped token), and any one of those baked in would exclude the other two.
|
|
8
|
+
*
|
|
9
|
+
* IT IS ALSO OPTIONAL. Nothing in the harness constructs one. A host that wants
|
|
10
|
+
* the control plane builds it and installs it; a host that does not carries on
|
|
11
|
+
* with local files and never learns this module exists. That is what keeps this
|
|
12
|
+
* from becoming a dependency prompd.app has to care about — prompd has no hosted
|
|
13
|
+
* sessions, no promote, and nothing to arbitrate.
|
|
14
|
+
*
|
|
15
|
+
* The API is a FACILITATOR, so this client is too: it reads and writes state,
|
|
16
|
+
* takes and releases the lease, and moves blobs. There is no `run`, no `start`,
|
|
17
|
+
* no `stop`. If a method ever appears here that makes something execute, the
|
|
18
|
+
* boundary moved and it should be argued rather than added.
|
|
19
|
+
*/
|
|
20
|
+
export class ApiError extends Error {
|
|
21
|
+
constructor(status, message, body) {
|
|
22
|
+
super(message);
|
|
23
|
+
Object.defineProperty(this, "status", {
|
|
24
|
+
enumerable: true,
|
|
25
|
+
configurable: true,
|
|
26
|
+
writable: true,
|
|
27
|
+
value: void 0
|
|
28
|
+
});
|
|
29
|
+
Object.defineProperty(this, "body", {
|
|
30
|
+
enumerable: true,
|
|
31
|
+
configurable: true,
|
|
32
|
+
writable: true,
|
|
33
|
+
value: void 0
|
|
34
|
+
});
|
|
35
|
+
this.name = 'ApiError';
|
|
36
|
+
this.status = status;
|
|
37
|
+
this.body = body;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/** A lease call that lost. Carries the epoch that won, because "re-read and
|
|
41
|
+
* decide" is always the caller's next step and the number is what it decides on. */
|
|
42
|
+
export class LeaseConflict extends ApiError {
|
|
43
|
+
constructor(status, message, body, currentEpoch) {
|
|
44
|
+
super(status, message, body);
|
|
45
|
+
Object.defineProperty(this, "currentEpoch", {
|
|
46
|
+
enumerable: true,
|
|
47
|
+
configurable: true,
|
|
48
|
+
writable: true,
|
|
49
|
+
value: void 0
|
|
50
|
+
});
|
|
51
|
+
this.name = 'LeaseConflict';
|
|
52
|
+
this.currentEpoch = currentEpoch;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
function rec(v) {
|
|
56
|
+
return v && typeof v === 'object' ? v : {};
|
|
57
|
+
}
|
|
58
|
+
export class CorenelApiClient {
|
|
59
|
+
constructor(t) {
|
|
60
|
+
Object.defineProperty(this, "t", {
|
|
61
|
+
enumerable: true,
|
|
62
|
+
configurable: true,
|
|
63
|
+
writable: true,
|
|
64
|
+
value: t
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
async request(path, init = {}) {
|
|
68
|
+
const token = await this.t.getToken();
|
|
69
|
+
const headers = new Headers(init.headers);
|
|
70
|
+
if (token)
|
|
71
|
+
headers.set('Authorization', `Bearer ${token}`);
|
|
72
|
+
if (init.body && !headers.has('Content-Type'))
|
|
73
|
+
headers.set('Content-Type', 'application/json');
|
|
74
|
+
const res = await this.t.fetch(`${this.t.baseUrl}${path}`, { ...init, headers });
|
|
75
|
+
const text = await res.text();
|
|
76
|
+
let body = null;
|
|
77
|
+
try {
|
|
78
|
+
body = text ? JSON.parse(text) : null;
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
body = text;
|
|
82
|
+
}
|
|
83
|
+
if (!res.ok) {
|
|
84
|
+
const message = String(rec(body).error || res.statusText || `HTTP ${res.status}`);
|
|
85
|
+
// 409 on a lease path is not a failure to report, it is an answer to act
|
|
86
|
+
// on; typing it separately keeps callers from string-matching a message.
|
|
87
|
+
if (res.status === 409 && 'epoch' in rec(body)) {
|
|
88
|
+
throw new LeaseConflict(res.status, message, body, Number(rec(body).epoch));
|
|
89
|
+
}
|
|
90
|
+
throw new ApiError(res.status, message, body);
|
|
91
|
+
}
|
|
92
|
+
return body;
|
|
93
|
+
}
|
|
94
|
+
/* ---- sessions ---- */
|
|
95
|
+
async listSessions() {
|
|
96
|
+
const body = rec(await this.request('/sessions'));
|
|
97
|
+
return Array.isArray(body.sessions) ? body.sessions : [];
|
|
98
|
+
}
|
|
99
|
+
async createSession(input = {}) {
|
|
100
|
+
const body = rec(await this.request('/sessions', { method: 'POST', body: JSON.stringify(input) }));
|
|
101
|
+
return body.session;
|
|
102
|
+
}
|
|
103
|
+
/** Find the remote record for a session this machine knows by another name.
|
|
104
|
+
* Null when there is not one yet — which is a normal answer on first push. */
|
|
105
|
+
async findByLocalId(localId) {
|
|
106
|
+
try {
|
|
107
|
+
const body = rec(await this.request(`/sessions/by-local/${encodeURIComponent(localId)}`));
|
|
108
|
+
return body.session;
|
|
109
|
+
}
|
|
110
|
+
catch (err) {
|
|
111
|
+
if (err instanceof ApiError && err.status === 404)
|
|
112
|
+
return null;
|
|
113
|
+
throw err;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
async getSession(id) {
|
|
117
|
+
const body = rec(await this.request(`/sessions/${encodeURIComponent(id)}`));
|
|
118
|
+
return body.session;
|
|
119
|
+
}
|
|
120
|
+
/* ---- the lease ---- */
|
|
121
|
+
/**
|
|
122
|
+
* Take the write lease.
|
|
123
|
+
*
|
|
124
|
+
* `expectedEpoch` is the caller's view; the server matches and increments in
|
|
125
|
+
* one conditional update. A `LeaseConflict` means someone else holds it — the
|
|
126
|
+
* caller re-reads and decides, and must NOT retry blindly, because retrying
|
|
127
|
+
* with the winning epoch is how you take a lease from a live writer.
|
|
128
|
+
*/
|
|
129
|
+
async acquireLease(sessionId, expectedEpoch, holder) {
|
|
130
|
+
const body = rec(await this.request(`/sessions/${encodeURIComponent(sessionId)}/lease`, {
|
|
131
|
+
method: 'POST',
|
|
132
|
+
body: JSON.stringify({ expectedEpoch, holder }),
|
|
133
|
+
}));
|
|
134
|
+
return body.lease;
|
|
135
|
+
}
|
|
136
|
+
async releaseLease(sessionId, expectedEpoch) {
|
|
137
|
+
const body = rec(await this.request(`/sessions/${encodeURIComponent(sessionId)}/lease`, {
|
|
138
|
+
method: 'DELETE',
|
|
139
|
+
body: JSON.stringify({ expectedEpoch }),
|
|
140
|
+
}));
|
|
141
|
+
return Number(body.epoch);
|
|
142
|
+
}
|
|
143
|
+
/* ---- state ---- */
|
|
144
|
+
/** Store the derived head. The epoch is mandatory: a stale writer would
|
|
145
|
+
* overwrite the real head with an older view of a timeline it never saw. */
|
|
146
|
+
async putHead(sessionId, epoch, head, status) {
|
|
147
|
+
await this.request(`/sessions/${encodeURIComponent(sessionId)}/state`, {
|
|
148
|
+
method: 'PUT',
|
|
149
|
+
body: JSON.stringify({ epoch, head, ...(status ? { status } : {}) }),
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
async appendTimeline(sessionId, epoch, entries) {
|
|
153
|
+
const body = rec(await this.request(`/sessions/${encodeURIComponent(sessionId)}/timeline`, {
|
|
154
|
+
method: 'POST',
|
|
155
|
+
body: JSON.stringify({ epoch, entries }),
|
|
156
|
+
}));
|
|
157
|
+
return { nextIndex: Number(body.nextIndex) };
|
|
158
|
+
}
|
|
159
|
+
async readTimeline(sessionId, since) {
|
|
160
|
+
const q = typeof since === 'number' ? `?since=${since}` : '';
|
|
161
|
+
const body = rec(await this.request(`/sessions/${encodeURIComponent(sessionId)}/timeline${q}`));
|
|
162
|
+
return {
|
|
163
|
+
entries: Array.isArray(body.entries) ? body.entries : [],
|
|
164
|
+
nextIndex: Number(body.nextIndex ?? 0),
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
/* ---- blobs ---- */
|
|
168
|
+
/** Which of these the store does NOT have. Batched on purpose: a capture is
|
|
169
|
+
* mostly unchanged files, and asking one at a time spends the whole capture on
|
|
170
|
+
* round trips. */
|
|
171
|
+
async missingBlobs(hashes) {
|
|
172
|
+
if (!hashes.length)
|
|
173
|
+
return [];
|
|
174
|
+
const body = rec(await this.request('/blobs/missing', {
|
|
175
|
+
method: 'POST',
|
|
176
|
+
body: JSON.stringify({ hashes }),
|
|
177
|
+
}));
|
|
178
|
+
return Array.isArray(body.missing) ? body.missing : [];
|
|
179
|
+
}
|
|
180
|
+
/** Upload bytes under their own hash. The server verifies before storing, so a
|
|
181
|
+
* mismatch here is a real error rather than something to paper over. */
|
|
182
|
+
async putBlob(hash, bytes) {
|
|
183
|
+
const body = rec(await this.request(`/blobs/${encodeURIComponent(hash)}`, {
|
|
184
|
+
method: 'PUT',
|
|
185
|
+
headers: { 'Content-Type': 'application/octet-stream' },
|
|
186
|
+
body: bytes,
|
|
187
|
+
}));
|
|
188
|
+
return { stored: body.stored === true };
|
|
189
|
+
}
|
|
190
|
+
async getBlob(hash) {
|
|
191
|
+
const token = await this.t.getToken();
|
|
192
|
+
const headers = new Headers();
|
|
193
|
+
if (token)
|
|
194
|
+
headers.set('Authorization', `Bearer ${token}`);
|
|
195
|
+
const res = await this.t.fetch(`${this.t.baseUrl}/blobs/${encodeURIComponent(hash)}`, { headers });
|
|
196
|
+
if (res.status === 404)
|
|
197
|
+
return null;
|
|
198
|
+
if (!res.ok)
|
|
199
|
+
throw new ApiError(res.status, res.statusText, null);
|
|
200
|
+
return new Uint8Array(await res.arrayBuffer());
|
|
201
|
+
}
|
|
202
|
+
/* ---- snapshots ---- */
|
|
203
|
+
async putSnapshot(input) {
|
|
204
|
+
const body = rec(await this.request('/snapshots', { method: 'POST', body: JSON.stringify(input) }));
|
|
205
|
+
return body.snapshot;
|
|
206
|
+
}
|
|
207
|
+
async getSnapshot(snapshotId) {
|
|
208
|
+
const body = rec(await this.request(`/snapshots/${encodeURIComponent(snapshotId)}`));
|
|
209
|
+
return body.snapshot;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../api/client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAYH,MAAM,OAAO,QAAS,SAAQ,KAAK;IAGjC,YAAY,MAAc,EAAE,OAAe,EAAE,IAAa;QACxD,KAAK,CAAC,OAAO,CAAC,CAAC;QAHR;;;;;WAAe;QACf;;;;;WAAc;QAGrB,IAAI,CAAC,IAAI,GAAG,UAAU,CAAC;QACvB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,CAAC;CACF;AAED;oFACoF;AACpF,MAAM,OAAO,aAAc,SAAQ,QAAQ;IAEzC,YAAY,MAAc,EAAE,OAAe,EAAE,IAAa,EAAE,YAAoB;QAC9E,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC;QAFtB;;;;;WAAqB;QAG5B,IAAI,CAAC,IAAI,GAAG,eAAe,CAAC;QAC5B,IAAI,CAAC,YAAY,GAAG,YAAY,CAAC;IACnC,CAAC;CACF;AAmBD,SAAS,GAAG,CAAC,CAAU;IACrB,OAAO,CAAC,IAAI,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAA4B,CAAC,CAAC,CAAC,EAAE,CAAC;AACxE,CAAC;AAED,MAAM,OAAO,gBAAgB;IAC3B,YAA6B,CAAe;QAAhC;;;;mBAAiB,CAAC;WAAc;IAAG,CAAC;IAExC,KAAK,CAAC,OAAO,CAAC,IAAY,EAAE,OAAoB,EAAE;QACxD,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;QACtC,MAAM,OAAO,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAC1C,IAAI,KAAK;YAAE,OAAO,CAAC,GAAG,CAAC,eAAe,EAAE,UAAU,KAAK,EAAE,CAAC,CAAC;QAC3D,IAAI,IAAI,CAAC,IAAI,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC;YAAE,OAAO,CAAC,GAAG,CAAC,cAAc,EAAE,kBAAkB,CAAC,CAAC;QAE/F,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,OAAO,GAAG,IAAI,EAAE,EAAE,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC;QACjF,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;QAC9B,IAAI,IAAI,GAAY,IAAI,CAAC;QACzB,IAAI,CAAC;YAAC,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;QAAC,CAAC;QAAC,MAAM,CAAC;YAAC,IAAI,GAAG,IAAI,CAAC;QAAC,CAAC;QAErE,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;YACZ,MAAM,OAAO,GAAG,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,KAAK,IAAI,GAAG,CAAC,UAAU,IAAI,QAAQ,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC;YAClF,yEAAyE;YACzE,yEAAyE;YACzE,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG,IAAI,OAAO,IAAI,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;gBAC/C,MAAM,IAAI,aAAa,CAAC,GAAG,CAAC,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC;YAC9E,CAAC;YACD,MAAM,IAAI,QAAQ,CAAC,GAAG,CAAC,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC;QAChD,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,wBAAwB;IAExB,KAAK,CAAC,YAAY;QAChB,MAAM,IAAI,GAAG,GAAG,CAAC,MAAM,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC;QAClD,OAAO,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,QAA2B,CAAC,CAAC,CAAC,EAAE,CAAC;IAC9E,CAAC;IAED,KAAK,CAAC,aAAa,CAAC,QAA6E,EAAE;QACjG,MAAM,IAAI,GAAG,GAAG,CAAC,MAAM,IAAI,CAAC,OAAO,CAAC,WAAW,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC;QACnG,OAAO,IAAI,CAAC,OAAwB,CAAC;IACvC,CAAC;IAED;kFAC8E;IAC9E,KAAK,CAAC,aAAa,CAAC,OAAe;QACjC,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,GAAG,CAAC,MAAM,IAAI,CAAC,OAAO,CAAC,sBAAsB,kBAAkB,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC;YAC1F,OAAO,IAAI,CAAC,OAAwB,CAAC;QACvC,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,GAAG,YAAY,QAAQ,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG;gBAAE,OAAO,IAAI,CAAC;YAC/D,MAAM,GAAG,CAAC;QACZ,CAAC;IACH,CAAC;IAED,KAAK,CAAC,UAAU,CAAC,EAAU;QACzB,MAAM,IAAI,GAAG,GAAG,CAAC,MAAM,IAAI,CAAC,OAAO,CAAC,aAAa,kBAAkB,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;QAC5E,OAAO,IAAI,CAAC,OAAwB,CAAC;IACvC,CAAC;IAED,yBAAyB;IAEzB;;;;;;;OAOG;IACH,KAAK,CAAC,YAAY,CAAC,SAAiB,EAAE,aAAqB,EAAE,MAAc;QACzE,MAAM,IAAI,GAAG,GAAG,CAAC,MAAM,IAAI,CAAC,OAAO,CAAC,aAAa,kBAAkB,CAAC,SAAS,CAAC,QAAQ,EAAE;YACtF,MAAM,EAAE,MAAM;YACd,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,aAAa,EAAE,MAAM,EAAE,CAAC;SAChD,CAAC,CAAC,CAAC;QACJ,OAAO,IAAI,CAAC,KAAoB,CAAC;IACnC,CAAC;IAED,KAAK,CAAC,YAAY,CAAC,SAAiB,EAAE,aAAqB;QACzD,MAAM,IAAI,GAAG,GAAG,CAAC,MAAM,IAAI,CAAC,OAAO,CAAC,aAAa,kBAAkB,CAAC,SAAS,CAAC,QAAQ,EAAE;YACtF,MAAM,EAAE,QAAQ;YAChB,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,aAAa,EAAE,CAAC;SACxC,CAAC,CAAC,CAAC;QACJ,OAAO,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC5B,CAAC;IAED,qBAAqB;IAErB;gFAC4E;IAC5E,KAAK,CAAC,OAAO,CAAC,SAAiB,EAAE,KAAa,EAAE,IAAa,EAAE,MAAe;QAC5E,MAAM,IAAI,CAAC,OAAO,CAAC,aAAa,kBAAkB,CAAC,SAAS,CAAC,QAAQ,EAAE;YACrE,MAAM,EAAE,KAAK;YACb,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;SACrE,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,cAAc,CAAC,SAAiB,EAAE,KAAa,EAAE,OAA2B;QAChF,MAAM,IAAI,GAAG,GAAG,CAAC,MAAM,IAAI,CAAC,OAAO,CAAC,aAAa,kBAAkB,CAAC,SAAS,CAAC,WAAW,EAAE;YACzF,MAAM,EAAE,MAAM;YACd,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC;SACzC,CAAC,CAAC,CAAC;QACJ,OAAO,EAAE,SAAS,EAAE,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC;IAC/C,CAAC;IAED,KAAK,CAAC,YAAY,CAAC,SAAiB,EAAE,KAAc;QAClD,MAAM,CAAC,GAAG,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,UAAU,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC7D,MAAM,IAAI,GAAG,GAAG,CAAC,MAAM,IAAI,CAAC,OAAO,CAAC,aAAa,kBAAkB,CAAC,SAAS,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC,CAAC;QAChG,OAAO;YACL,OAAO,EAAE,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE;YACxD,SAAS,EAAE,MAAM,CAAC,IAAI,CAAC,SAAS,IAAI,CAAC,CAAC;SACvC,CAAC;IACJ,CAAC;IAED,qBAAqB;IAErB;;sBAEkB;IAClB,KAAK,CAAC,YAAY,CAAC,MAAyB;QAC1C,IAAI,CAAC,MAAM,CAAC,MAAM;YAAE,OAAO,EAAE,CAAC;QAC9B,MAAM,IAAI,GAAG,GAAG,CAAC,MAAM,IAAI,CAAC,OAAO,CAAC,gBAAgB,EAAE;YACpD,MAAM,EAAE,MAAM;YACd,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;SACjC,CAAC,CAAC,CAAC;QACJ,OAAO,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,OAAmB,CAAC,CAAC,CAAC,EAAE,CAAC;IACrE,CAAC;IAED;4EACwE;IACxE,KAAK,CAAC,OAAO,CAAC,IAAY,EAAE,KAAiB;QAC3C,MAAM,IAAI,GAAG,GAAG,CAAC,MAAM,IAAI,CAAC,OAAO,CAAC,UAAU,kBAAkB,CAAC,IAAI,CAAC,EAAE,EAAE;YACxE,MAAM,EAAE,KAAK;YACb,OAAO,EAAE,EAAE,cAAc,EAAE,0BAA0B,EAAE;YACvD,IAAI,EAAE,KAA4B;SACnC,CAAC,CAAC,CAAC;QACJ,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,KAAK,IAAI,EAAE,CAAC;IAC1C,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,IAAY;QACxB,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;QACtC,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;QAC9B,IAAI,KAAK;YAAE,OAAO,CAAC,GAAG,CAAC,eAAe,EAAE,UAAU,KAAK,EAAE,CAAC,CAAC;QAC3D,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,OAAO,UAAU,kBAAkB,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC;QACnG,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG;YAAE,OAAO,IAAI,CAAC;QACpC,IAAI,CAAC,GAAG,CAAC,EAAE;YAAE,MAAM,IAAI,QAAQ,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;QAClE,OAAO,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,WAAW,EAAE,CAAC,CAAC;IACjD,CAAC;IAED,yBAAyB;IAEzB,KAAK,CAAC,WAAW,CAAC,KAQjB;QACC,MAAM,IAAI,GAAG,GAAG,CAAC,MAAM,IAAI,CAAC,OAAO,CAAC,YAAY,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC;QACpG,OAAO,IAAI,CAAC,QAAQ,CAAC;IACvB,CAAC;IAED,KAAK,CAAC,WAAW,CAAC,UAAkB;QAClC,MAAM,IAAI,GAAG,GAAG,CAAC,MAAM,IAAI,CAAC,OAAO,CAAC,cAAc,kBAAkB,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC,CAAC;QACrF,OAAO,IAAI,CAAC,QAAQ,CAAC;IACvB,CAAC;CACF"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { FileService } from '@corenel/protocol';
|
|
2
|
+
import type { MirrorLocalSource } from './mirror';
|
|
3
|
+
export interface FileMirrorSourceDeps {
|
|
4
|
+
/** The workspace holding the session directories. Resolved per call, so a
|
|
5
|
+
* source switch (a different root, a reconnect) is picked up without rebuilding
|
|
6
|
+
* the mirror. */
|
|
7
|
+
files: () => Promise<FileService | null>;
|
|
8
|
+
/**
|
|
9
|
+
* Where a session's directory lives.
|
|
10
|
+
*
|
|
11
|
+
* A FUNCTION, not a constant, because sessions do not all live in one place:
|
|
12
|
+
* a crew agent's are under `crew/<agent>/sessions`, a chat's under the
|
|
13
|
+
* workspace's own root. A single path would silently mirror nothing for
|
|
14
|
+
* whichever kind it did not name — the worst failure available to a mirror,
|
|
15
|
+
* since it looks exactly like a session with no activity.
|
|
16
|
+
*
|
|
17
|
+
* Return null for a session this machine cannot locate.
|
|
18
|
+
*/
|
|
19
|
+
sessionsRoot: string | ((sessionId: string) => string | null);
|
|
20
|
+
/** For the derived head's timestamp. */
|
|
21
|
+
now?: () => number;
|
|
22
|
+
}
|
|
23
|
+
export declare function makeFileMirrorSource(deps: FileMirrorSourceDeps): MirrorLocalSource;
|
|
24
|
+
//# sourceMappingURL=fileMirrorSource.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fileMirrorSource.d.ts","sourceRoot":"","sources":["../../api/fileMirrorSource.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAGrD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAElD,MAAM,WAAW,oBAAoB;IACnC;;qBAEiB;IACjB,KAAK,EAAE,MAAM,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;IACzC;;;;;;;;;;OAUG;IACH,YAAY,EAAE,MAAM,GAAG,CAAC,CAAC,SAAS,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC,CAAC;IAC9D,wCAAwC;IACxC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;AAED,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,oBAAoB,GAAG,iBAAiB,CAuDlF"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { foldTimeline } from '../sessions/timeline';
|
|
2
|
+
import { deriveHead, parseHead } from '../sessions/head';
|
|
3
|
+
export function makeFileMirrorSource(deps) {
|
|
4
|
+
const now = deps.now ?? Date.now;
|
|
5
|
+
const rootFor = (sessionId) => (typeof deps.sessionsRoot === 'function' ? deps.sessionsRoot(sessionId) : deps.sessionsRoot);
|
|
6
|
+
const logPath = (sessionId) => {
|
|
7
|
+
const root = rootFor(sessionId);
|
|
8
|
+
return root ? `${root}/${sessionId}/SESSION.jsonl` : null;
|
|
9
|
+
};
|
|
10
|
+
const headPath = (sessionId) => {
|
|
11
|
+
const root = rootFor(sessionId);
|
|
12
|
+
return root ? `${root}/${sessionId}/state.json` : null;
|
|
13
|
+
};
|
|
14
|
+
const readText = async (path) => {
|
|
15
|
+
if (!path)
|
|
16
|
+
return '';
|
|
17
|
+
const fs = await deps.files();
|
|
18
|
+
if (!fs)
|
|
19
|
+
return '';
|
|
20
|
+
try {
|
|
21
|
+
return await fs.read(path);
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
// Absent is normal: a session that has not been written yet, or a workspace
|
|
25
|
+
// that moved. Nothing to mirror is not a failure to mirror.
|
|
26
|
+
return '';
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
return {
|
|
30
|
+
async entriesSince(sessionId, index) {
|
|
31
|
+
const { entries, nextIndex } = foldTimeline(await readText(logPath(sessionId)));
|
|
32
|
+
// Filtered AFTER folding, never before: superseding and compaction are
|
|
33
|
+
// decided across the whole log, and a fold of a suffix would resurrect an
|
|
34
|
+
// entry a later line replaced.
|
|
35
|
+
return { entries: entries.filter((e) => e.index >= index), nextIndex };
|
|
36
|
+
},
|
|
37
|
+
async head(sessionId) {
|
|
38
|
+
const stored = parseHead(await readText(headPath(sessionId)));
|
|
39
|
+
if (stored)
|
|
40
|
+
return stored;
|
|
41
|
+
const text = await readText(logPath(sessionId));
|
|
42
|
+
if (!text.trim())
|
|
43
|
+
return null;
|
|
44
|
+
const { entries, nextIndex } = foldTimeline(text);
|
|
45
|
+
if (!entries.length)
|
|
46
|
+
return null;
|
|
47
|
+
// Rebuilt rather than skipped. A missing head means "fold the log again",
|
|
48
|
+
// and mirroring a session with no head at all would leave the far end
|
|
49
|
+
// unable to say what it was waiting on.
|
|
50
|
+
return deriveHead({
|
|
51
|
+
sessionId,
|
|
52
|
+
entries,
|
|
53
|
+
nextIndex,
|
|
54
|
+
updatedAt: new Date(now()).toISOString(),
|
|
55
|
+
});
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=fileMirrorSource.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fileMirrorSource.js","sourceRoot":"","sources":["../../api/fileMirrorSource.ts"],"names":[],"mappings":"AAmBA,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACpD,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAwBzD,MAAM,UAAU,oBAAoB,CAAC,IAA0B;IAC7D,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC;IACjC,MAAM,OAAO,GAAG,CAAC,SAAiB,EAAiB,EAAE,CAAC,CACpD,OAAO,IAAI,CAAC,YAAY,KAAK,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,YAAY,CAC3F,CAAC;IACF,MAAM,OAAO,GAAG,CAAC,SAAiB,EAAiB,EAAE;QACnD,MAAM,IAAI,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;QAChC,OAAO,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,IAAI,SAAS,gBAAgB,CAAC,CAAC,CAAC,IAAI,CAAC;IAC5D,CAAC,CAAC;IACF,MAAM,QAAQ,GAAG,CAAC,SAAiB,EAAiB,EAAE;QACpD,MAAM,IAAI,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;QAChC,OAAO,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,IAAI,SAAS,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC;IACzD,CAAC,CAAC;IAEF,MAAM,QAAQ,GAAG,KAAK,EAAE,IAAmB,EAAmB,EAAE;QAC9D,IAAI,CAAC,IAAI;YAAE,OAAO,EAAE,CAAC;QACrB,MAAM,EAAE,GAAG,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC;QAC9B,IAAI,CAAC,EAAE;YAAE,OAAO,EAAE,CAAC;QACnB,IAAI,CAAC;YACH,OAAO,MAAM,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC7B,CAAC;QAAC,MAAM,CAAC;YACP,4EAA4E;YAC5E,4DAA4D;YAC5D,OAAO,EAAE,CAAC;QACZ,CAAC;IACH,CAAC,CAAC;IAEF,OAAO;QACL,KAAK,CAAC,YAAY,CAAC,SAAS,EAAE,KAAK;YACjC,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,GAAG,YAAY,CAAC,MAAM,QAAQ,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;YAChF,uEAAuE;YACvE,0EAA0E;YAC1E,+BAA+B;YAC/B,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,IAAI,KAAK,CAAC,EAAE,SAAS,EAAE,CAAC;QACzE,CAAC;QAED,KAAK,CAAC,IAAI,CAAC,SAAS;YAClB,MAAM,MAAM,GAAG,SAAS,CAAC,MAAM,QAAQ,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;YAC9D,IAAI,MAAM;gBAAE,OAAO,MAAM,CAAC;YAE1B,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC;YAChD,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE;gBAAE,OAAO,IAAI,CAAC;YAC9B,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;YAClD,IAAI,CAAC,OAAO,CAAC,MAAM;gBAAE,OAAO,IAAI,CAAC;YACjC,0EAA0E;YAC1E,sEAAsE;YACtE,wCAAwC;YACxC,OAAO,UAAU,CAAC;gBAChB,SAAS;gBACT,OAAO;gBACP,SAAS;gBACT,SAAS,EAAE,IAAI,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,WAAW,EAAE;aACzC,CAAC,CAAC;QACL,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import type { CorenelApiClient } from './client';
|
|
2
|
+
/** What the local side must expose for its state to be mirrored. Deliberately
|
|
3
|
+
* read-only: a mirror never writes locally. */
|
|
4
|
+
export interface MirrorLocalSource {
|
|
5
|
+
/** Timeline entries at or after `index`, plus the local high-water mark. */
|
|
6
|
+
entriesSince(sessionId: string, index: number): Promise<{
|
|
7
|
+
entries: unknown[];
|
|
8
|
+
nextIndex: number;
|
|
9
|
+
}>;
|
|
10
|
+
/** The derived head, or null when there is not one yet. */
|
|
11
|
+
head(sessionId: string): Promise<unknown | null>;
|
|
12
|
+
}
|
|
13
|
+
/** Where a session's mirror has got to. Rendered, logged, and used to resume. */
|
|
14
|
+
export interface MirrorStatus {
|
|
15
|
+
/** Local index up to which everything is known to be mirrored. */
|
|
16
|
+
pushedIndex: number;
|
|
17
|
+
/** Epoch ms of the last successful push, or 0. */
|
|
18
|
+
lastPushedAt: number;
|
|
19
|
+
/** Why the last attempt failed, or '' when the last attempt succeeded. */
|
|
20
|
+
lastError: string;
|
|
21
|
+
/** True when this machine holds authority and may push. */
|
|
22
|
+
authoritative: boolean;
|
|
23
|
+
/** The epoch this machine last saw. */
|
|
24
|
+
epoch: number;
|
|
25
|
+
}
|
|
26
|
+
export interface PushResult {
|
|
27
|
+
ok: boolean;
|
|
28
|
+
/** Entries actually sent this time. */
|
|
29
|
+
pushed: number;
|
|
30
|
+
status: MirrorStatus;
|
|
31
|
+
/** Set when authority was lost — the caller must STOP pushing, not retry. */
|
|
32
|
+
lostAuthority?: boolean;
|
|
33
|
+
}
|
|
34
|
+
export interface SessionMirrorDeps {
|
|
35
|
+
client: CorenelApiClient;
|
|
36
|
+
local: MirrorLocalSource;
|
|
37
|
+
/** Identifies this machine in the lease. A human-readable name is fine; it is
|
|
38
|
+
* for diagnostics, never for authorization. */
|
|
39
|
+
holder: string;
|
|
40
|
+
now?: () => number;
|
|
41
|
+
log?: (line: string) => void;
|
|
42
|
+
}
|
|
43
|
+
export declare class SessionMirror {
|
|
44
|
+
private readonly deps;
|
|
45
|
+
private readonly state;
|
|
46
|
+
/** local session id -> the id this backend knows it by. Two namespaces, and
|
|
47
|
+
* assuming they are one is a silent 404 on every push. */
|
|
48
|
+
private readonly remoteIds;
|
|
49
|
+
private readonly now;
|
|
50
|
+
constructor(deps: SessionMirrorDeps);
|
|
51
|
+
/**
|
|
52
|
+
* The backend's id for a local session, creating the record if this is the
|
|
53
|
+
* first time it has been mentioned.
|
|
54
|
+
*
|
|
55
|
+
* Idempotent on the server, so a reconnect finds the session it was already
|
|
56
|
+
* pushing to rather than opening a second one beside it and splitting the log.
|
|
57
|
+
*/
|
|
58
|
+
private remoteId;
|
|
59
|
+
status(sessionId: string): MirrorStatus;
|
|
60
|
+
private set;
|
|
61
|
+
/**
|
|
62
|
+
* Claim authority for this machine.
|
|
63
|
+
*
|
|
64
|
+
* Reads the current epoch and takes the next one. Losing the race is a normal
|
|
65
|
+
* answer, not an error: another machine holds the session, and what this one
|
|
66
|
+
* does about that is a decision for the caller (usually: stop, and offer the
|
|
67
|
+
* user a demote).
|
|
68
|
+
*/
|
|
69
|
+
claim(sessionId: string): Promise<{
|
|
70
|
+
ok: boolean;
|
|
71
|
+
status: MirrorStatus;
|
|
72
|
+
}>;
|
|
73
|
+
/** Give authority up, so another machine can take it without waiting for an
|
|
74
|
+
* expiry that does not exist. Best-effort like everything else here. */
|
|
75
|
+
release(sessionId: string): Promise<void>;
|
|
76
|
+
/**
|
|
77
|
+
* Push everything the mirror has not seen.
|
|
78
|
+
*
|
|
79
|
+
* Resumes from the recorded cursor, so an interrupted push costs a retry rather
|
|
80
|
+
* than a re-upload of the whole log. Resolves either way — a caller in the
|
|
81
|
+
* middle of an agent turn must never have to handle this throwing.
|
|
82
|
+
*/
|
|
83
|
+
push(sessionId: string): Promise<PushResult>;
|
|
84
|
+
/**
|
|
85
|
+
* Read the mirror.
|
|
86
|
+
*
|
|
87
|
+
* RETURNS the remote state; it does not write anything locally. What to do with
|
|
88
|
+
* a remote copy — restore it, merge it, show it read-only — belongs to whoever
|
|
89
|
+
* owns the local store, and a mirror that silently overwrote local state would
|
|
90
|
+
* have made itself the original.
|
|
91
|
+
*/
|
|
92
|
+
pull(sessionId: string, since?: number): Promise<{
|
|
93
|
+
ok: boolean;
|
|
94
|
+
entries: unknown[];
|
|
95
|
+
nextIndex: number;
|
|
96
|
+
error: string;
|
|
97
|
+
}>;
|
|
98
|
+
/** Forget local bookkeeping for a session — after a destroy, or when this
|
|
99
|
+
* machine stops caring about it. Does not touch the remote copy. */
|
|
100
|
+
forget(sessionId: string): void;
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=mirror.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mirror.d.ts","sourceRoot":"","sources":["../../api/mirror.ts"],"names":[],"mappings":"AA+BA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAGjD;+CAC+C;AAC/C,MAAM,WAAW,iBAAiB;IAChC,4EAA4E;IAC5E,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,EAAE,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACnG,2DAA2D;IAC3D,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC;CAClD;AAED,iFAAiF;AACjF,MAAM,WAAW,YAAY;IAC3B,kEAAkE;IAClE,WAAW,EAAE,MAAM,CAAC;IACpB,kDAAkD;IAClD,YAAY,EAAE,MAAM,CAAC;IACrB,0EAA0E;IAC1E,SAAS,EAAE,MAAM,CAAC;IAClB,2DAA2D;IAC3D,aAAa,EAAE,OAAO,CAAC;IACvB,uCAAuC;IACvC,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,OAAO,CAAC;IACZ,uCAAuC;IACvC,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,YAAY,CAAC;IACrB,6EAA6E;IAC7E,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,gBAAgB,CAAC;IACzB,KAAK,EAAE,iBAAiB,CAAC;IACzB;mDAC+C;IAC/C,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CAC9B;AAMD,qBAAa,aAAa;IAOZ,OAAO,CAAC,QAAQ,CAAC,IAAI;IANjC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAmC;IACzD;8DAC0D;IAC1D,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA6B;IACvD,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;gBAEN,IAAI,EAAE,iBAAiB;IAIpD;;;;;;OAMG;YACW,QAAQ;IA8BtB,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,YAAY;IAIvC,OAAO,CAAC,GAAG;IAMX;;;;;;;OAOG;IACG,KAAK,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,EAAE,EAAE,OAAO,CAAC;QAAC,MAAM,EAAE,YAAY,CAAA;KAAE,CAAC;IAsB9E;4EACwE;IAClE,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAc/C;;;;;;OAMG;IACG,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;IAkDlD;;;;;;;OAOG;IACG,IAAI,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,SAAI,GAAG,OAAO,CAAC;QAAE,EAAE,EAAE,OAAO,CAAC;QAAC,OAAO,EAAE,OAAO,EAAE,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IASxH;wEACoE;IACpE,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;CAIhC"}
|