@cueloop/daemon 0.1.0-alpha.23 → 0.1.0-alpha.25
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/package.json +2 -2
- package/src/api.ts +18 -0
- package/src/client.ts +8 -0
- package/src/server.test.ts +35 -0
- package/src/server.ts +8 -0
- package/src/validate.test.ts +2 -0
- package/src/validate.ts +8 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cueloop/daemon",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.25",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"./share-blob": "./src/share-blob.ts"
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@cueloop/schema": "0.1.0-alpha.
|
|
14
|
+
"@cueloop/schema": "0.1.0-alpha.25",
|
|
15
15
|
"valibot": "^1.4.2"
|
|
16
16
|
},
|
|
17
17
|
"publishConfig": {
|
package/src/api.ts
CHANGED
|
@@ -168,6 +168,24 @@ export class DaemonCore {
|
|
|
168
168
|
return session;
|
|
169
169
|
}
|
|
170
170
|
|
|
171
|
+
sessionSetShareId(id: string, shareId: string): ReviewSession {
|
|
172
|
+
const session = this.mutable(id);
|
|
173
|
+
session.shareId = shareId;
|
|
174
|
+
this.store.upsert(session);
|
|
175
|
+
this.emit("session.updated", id);
|
|
176
|
+
return session;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/** Union incoming annotations in by id; existing ids (the planner's) win. */
|
|
180
|
+
sessionMergeAnnotations(id: string, incoming: Annotation[]): ReviewSession {
|
|
181
|
+
const session = this.mutable(id);
|
|
182
|
+
const known = new Set(session.annotations.map((annotation) => annotation.id));
|
|
183
|
+
for (const annotation of incoming) if (!known.has(annotation.id)) session.annotations.push(annotation);
|
|
184
|
+
this.store.upsert(session);
|
|
185
|
+
this.emit("session.updated", id);
|
|
186
|
+
return session;
|
|
187
|
+
}
|
|
188
|
+
|
|
171
189
|
sessionResolve(id: string, verdictKind: VerdictKind, summary: string): ReviewSession {
|
|
172
190
|
const session = this.mutable(id);
|
|
173
191
|
const verdict: Verdict = {
|
package/src/client.ts
CHANGED
|
@@ -34,6 +34,8 @@ export interface SessionClient {
|
|
|
34
34
|
sessionRemoveAnnotation(id: string, annotationId: string): Promise<ReviewSession>;
|
|
35
35
|
sessionSetWorkingCopy(id: string, workingCopy: string | undefined): Promise<ReviewSession>;
|
|
36
36
|
sessionSetViewed(id: string, viewedPaths: string[]): Promise<ReviewSession>;
|
|
37
|
+
sessionSetShareId(id: string, shareId: string): Promise<ReviewSession>;
|
|
38
|
+
sessionMergeAnnotations(id: string, annotations: Annotation[]): Promise<ReviewSession>;
|
|
37
39
|
sessionResolve(id: string, verdictKind: VerdictKind, summary: string): Promise<ReviewSession>;
|
|
38
40
|
close(): void;
|
|
39
41
|
}
|
|
@@ -183,6 +185,12 @@ export class DaemonClient implements SessionClient {
|
|
|
183
185
|
sessionSetViewed(id: string, viewedPaths: string[]): Promise<ReviewSession> {
|
|
184
186
|
return this.request("session.setViewed", { id, viewedPaths });
|
|
185
187
|
}
|
|
188
|
+
sessionSetShareId(id: string, shareId: string): Promise<ReviewSession> {
|
|
189
|
+
return this.request("session.setShareId", { id, shareId });
|
|
190
|
+
}
|
|
191
|
+
sessionMergeAnnotations(id: string, annotations: Annotation[]): Promise<ReviewSession> {
|
|
192
|
+
return this.request("session.mergeAnnotations", { id, annotations });
|
|
193
|
+
}
|
|
186
194
|
sessionResolve(id: string, verdictKind: VerdictKind, summary: string): Promise<ReviewSession> {
|
|
187
195
|
return this.request("session.resolve", { id, verdictKind, summary });
|
|
188
196
|
}
|
package/src/server.test.ts
CHANGED
|
@@ -121,6 +121,41 @@ describe("socket round-trip", () => {
|
|
|
121
121
|
expect(got.artifact.meta.agent).toBe("claude-code");
|
|
122
122
|
});
|
|
123
123
|
|
|
124
|
+
test("share-sync verbs round-trip: setShareId persists, mergeAnnotations unions by id", async () => {
|
|
125
|
+
// Arrange
|
|
126
|
+
const session = await client.sessionCreate(WS, PLAN);
|
|
127
|
+
const anchor = { quote: "Body text", prefix: "", suffix: "." };
|
|
128
|
+
|
|
129
|
+
// Act - stamp the share id, then a local note
|
|
130
|
+
await client.sessionSetShareId(session.id, "p_abc123xy");
|
|
131
|
+
await client.sessionAnnotate(session.id, { id: "a1", kind: "comment", anchor, body: "mine" });
|
|
132
|
+
|
|
133
|
+
// Assert - the share id came back over the wire
|
|
134
|
+
expect((await client.sessionGet(session.id)).shareId).toBe("p_abc123xy");
|
|
135
|
+
|
|
136
|
+
// Act - merge an update to the known id plus a new collaborator note
|
|
137
|
+
const merged = await client.sessionMergeAnnotations(session.id, [
|
|
138
|
+
{ id: "a1", kind: "comment", anchor, body: "should not overwrite", createdAt: "2026-01-01T00:00:00.000Z" },
|
|
139
|
+
{ id: "a2", kind: "comment", anchor, body: "theirs", author: "SHA256:mate", createdAt: "2026-01-01T00:00:00.000Z" },
|
|
140
|
+
]);
|
|
141
|
+
|
|
142
|
+
// Assert - union by id: existing a1 kept as-is, a2 added with its author
|
|
143
|
+
expect(merged.annotations.map((annotation) => annotation.id).sort()).toEqual(["a1", "a2"]);
|
|
144
|
+
expect(merged.annotations.find((annotation) => annotation.id === "a1")!.body).toBe("mine");
|
|
145
|
+
expect(merged.annotations.find((annotation) => annotation.id === "a2")!.author).toBe("SHA256:mate");
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test("mergeAnnotations validates each annotation at the socket boundary", async () => {
|
|
149
|
+
const session = await client.sessionCreate(WS, PLAN);
|
|
150
|
+
try {
|
|
151
|
+
// @ts-expect-error intentionally malformed annotation (empty id, missing fields)
|
|
152
|
+
await client.sessionMergeAnnotations(session.id, [{ id: "" }]);
|
|
153
|
+
throw new Error("should have thrown");
|
|
154
|
+
} catch (error) {
|
|
155
|
+
expect((error as DaemonClientError).code).toBe("invalid_params");
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
|
|
124
159
|
test("two clients see the same state (thin-renderer model)", async () => {
|
|
125
160
|
// Arrange
|
|
126
161
|
const second = await DaemonClient.connect({ home });
|
package/src/server.ts
CHANGED
|
@@ -245,6 +245,14 @@ export class DaemonServer {
|
|
|
245
245
|
const params = parseParams("session.setViewed", request.params);
|
|
246
246
|
return core.sessionSetViewed(params.id, params.viewedPaths);
|
|
247
247
|
}
|
|
248
|
+
case "session.setShareId": {
|
|
249
|
+
const params = parseParams("session.setShareId", request.params);
|
|
250
|
+
return core.sessionSetShareId(params.id, params.shareId);
|
|
251
|
+
}
|
|
252
|
+
case "session.mergeAnnotations": {
|
|
253
|
+
const params = parseParams("session.mergeAnnotations", request.params);
|
|
254
|
+
return core.sessionMergeAnnotations(params.id, params.annotations);
|
|
255
|
+
}
|
|
248
256
|
case "session.resolve": {
|
|
249
257
|
const params = parseParams("session.resolve", request.params);
|
|
250
258
|
return core.sessionResolve(params.id, params.verdictKind, params.summary);
|
package/src/validate.test.ts
CHANGED
package/src/validate.ts
CHANGED
|
@@ -83,6 +83,9 @@ export const AnnotationSchema = v.object({
|
|
|
83
83
|
|
|
84
84
|
const SessionId = NonEmpty;
|
|
85
85
|
|
|
86
|
+
/** A stored annotation: the wire shape plus the daemon-stamped createdAt. */
|
|
87
|
+
export const FullAnnotationSchema = v.object({ ...AnnotationSchema.entries, createdAt: v.string() } satisfies EntriesOf<Annotation>);
|
|
88
|
+
|
|
86
89
|
export const Params = {
|
|
87
90
|
"session.create": v.object({ workspace: WorkspaceSchema, artifact: ArtifactSchema }),
|
|
88
91
|
"session.get": v.object({ id: SessionId }),
|
|
@@ -103,6 +106,8 @@ export const Params = {
|
|
|
103
106
|
"session.removeAnnotation": v.object({ id: SessionId, annotationId: NonEmpty }),
|
|
104
107
|
"session.setWorkingCopy": v.object({ id: SessionId, workingCopy: v.optional(v.string()) }),
|
|
105
108
|
"session.setViewed": v.object({ id: SessionId, viewedPaths: v.array(v.string()) }),
|
|
109
|
+
"session.setShareId": v.object({ id: SessionId, shareId: NonEmpty }),
|
|
110
|
+
"session.mergeAnnotations": v.object({ id: SessionId, annotations: v.array(FullAnnotationSchema) }),
|
|
106
111
|
"session.resolve": v.object({
|
|
107
112
|
id: SessionId,
|
|
108
113
|
verdictKind: v.picklist(["comment", "approve", "request_changes"]),
|
|
@@ -156,12 +161,14 @@ export const SessionRecordSchema = v.object({
|
|
|
156
161
|
workspace: WorkspaceSchema,
|
|
157
162
|
artifact: ArtifactSchema,
|
|
158
163
|
revisions: v.array(RevisionSchema),
|
|
159
|
-
annotations: v.array(
|
|
164
|
+
annotations: v.array(FullAnnotationSchema),
|
|
160
165
|
workingCopy: v.optional(v.string()),
|
|
161
166
|
viewedPaths: v.optional(v.array(v.string())),
|
|
162
167
|
verdict: v.nullable(VerdictSchema),
|
|
163
168
|
status: v.picklist(["pending", "resolved"]),
|
|
164
169
|
createdAt: v.string(),
|
|
170
|
+
shareId: v.optional(v.string()),
|
|
171
|
+
owner: v.optional(v.string()),
|
|
165
172
|
} satisfies EntriesOf<ReviewSession>);
|
|
166
173
|
|
|
167
174
|
export function validateSessionRecord(raw: unknown): { ok: true; value: v.InferOutput<typeof SessionRecordSchema> } | { ok: false; error: string } {
|