@cueloop/daemon 0.1.0-alpha.102 → 0.1.0-alpha.103
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 +3 -3
- package/src/agent-tools.ts +6 -0
- package/src/api.ts +63 -0
- package/src/client-reliability.test.ts +2 -0
- package/src/client.ts +24 -2
- package/src/curate.ts +2 -0
- package/src/daemon-sdk.ts +8 -0
- package/src/diff-hot-reload.test.ts +4 -0
- package/src/diff-watcher.test.ts +2 -0
- package/src/diff-watcher.ts +33 -0
- package/src/examples/daemon-sdk.ts +2 -0
- package/src/gh.ts +4 -0
- package/src/ghostty-thread-surface-store.ts +3 -0
- package/src/ghostty-thread-surface.test.ts +2 -0
- package/src/ghostty-thread-surface.ts +6 -0
- package/src/harness-diagnostics.ts +1 -0
- package/src/harness-handoff.test.ts +725 -0
- package/src/harness-handoff.ts +27 -0
- package/src/harness-state-store.ts +5 -0
- package/src/herdr-split.ts +5 -0
- package/src/herdr-thread-surface.ts +14 -0
- package/src/interruptible-wait.ts +3 -0
- package/src/jj-working-tree.ts +9 -0
- package/src/main.ts +1 -0
- package/src/operation-receipts.ts +6 -0
- package/src/pr-poller.ts +7 -0
- package/src/project-files.ts +4 -0
- package/src/protocol.ts +4 -0
- package/src/retention.ts +8 -0
- package/src/server.ts +36 -7
- package/src/share-blob.test.ts +2 -0
- package/src/single-instance.test.ts +3 -0
- package/src/store.ts +11 -0
- package/src/thread-agent-validation.ts +20 -0
- package/src/thread-agent.test.ts +14 -1
- package/src/thread-agent.ts +353 -24
- package/src/thread-review.test.ts +4 -0
- package/src/thread-review.ts +8 -0
- package/src/thread-subscription.test.ts +3 -0
- package/src/thread-subscription.ts +9 -0
- package/src/validate.test.ts +4 -0
- package/src/validate.ts +3 -0
- package/src/vcs-source.ts +6 -0
- package/src/working-tree.ts +4 -0
|
@@ -213,6 +213,7 @@ describe("one daemon per home", () => {
|
|
|
213
213
|
expect(existsSync(path!)).toBe(true);
|
|
214
214
|
} finally {
|
|
215
215
|
client.close();
|
|
216
|
+
|
|
216
217
|
if (previousTimeout === undefined) delete process.env.CUELOOP_START_TIMEOUT_MS;
|
|
217
218
|
else process.env.CUELOOP_START_TIMEOUT_MS = previousTimeout;
|
|
218
219
|
}
|
|
@@ -242,7 +243,9 @@ describe("one daemon per home", () => {
|
|
|
242
243
|
expect((await client.ping()).pid).toBe(process.pid);
|
|
243
244
|
} finally {
|
|
244
245
|
client.close();
|
|
246
|
+
|
|
245
247
|
if (transient.listening) transient.close();
|
|
248
|
+
|
|
246
249
|
if (previousTimeout === undefined) delete process.env.CUELOOP_START_TIMEOUT_MS;
|
|
247
250
|
else process.env.CUELOOP_START_TIMEOUT_MS = previousTimeout;
|
|
248
251
|
}
|
package/src/store.ts
CHANGED
|
@@ -85,8 +85,10 @@ export class ThreadStore implements ThreadRepository {
|
|
|
85
85
|
const bucketPath = join(root, bucket);
|
|
86
86
|
|
|
87
87
|
if (!statSync(bucketPath).isDirectory()) continue;
|
|
88
|
+
|
|
88
89
|
for (const file of readdirSync(bucketPath)) {
|
|
89
90
|
if (!file.endsWith(".jsonl")) continue;
|
|
91
|
+
|
|
90
92
|
const filePath = join(bucketPath, file);
|
|
91
93
|
|
|
92
94
|
try {
|
|
@@ -96,6 +98,7 @@ export class ThreadStore implements ThreadRepository {
|
|
|
96
98
|
this.files.set(session.id, filePath);
|
|
97
99
|
this.lineCounts.set(session.id, lines);
|
|
98
100
|
report.recovered.push(session.id);
|
|
101
|
+
|
|
99
102
|
// a torn trailing fragment would fuse with the next append and lose that update, so rewrite
|
|
100
103
|
// the log to a single clean snapshot line now that the last valid record is in hand
|
|
101
104
|
if (torn) this.writeWhole(session);
|
|
@@ -117,10 +120,12 @@ export class ThreadStore implements ThreadRepository {
|
|
|
117
120
|
const legacyDir = sessionsDir(this.home);
|
|
118
121
|
|
|
119
122
|
if (!existsSync(legacyDir)) return;
|
|
123
|
+
|
|
120
124
|
const parked = migratedSessionsDir(this.home);
|
|
121
125
|
|
|
122
126
|
for (const file of readdirSync(legacyDir)) {
|
|
123
127
|
if (!file.endsWith(".json")) continue;
|
|
128
|
+
|
|
124
129
|
const legacyPath = join(legacyDir, file);
|
|
125
130
|
let session: Thread;
|
|
126
131
|
|
|
@@ -128,6 +133,7 @@ export class ThreadStore implements ThreadRepository {
|
|
|
128
133
|
const parsed = validateThreadRecord(JSON.parse(readFileSync(legacyPath, "utf8")));
|
|
129
134
|
|
|
130
135
|
if (!parsed.ok) throw new Error(`invalid record - ${parsed.error}`);
|
|
136
|
+
|
|
131
137
|
session = withHistory(parsed.value);
|
|
132
138
|
} catch (error) {
|
|
133
139
|
report.skipped.push({
|
|
@@ -144,6 +150,7 @@ export class ThreadStore implements ThreadRepository {
|
|
|
144
150
|
// after a rollback a newer JSONL can already hold this thread; never overwrite it with the
|
|
145
151
|
// older legacy snapshot - the existing log stays authoritative and the threads scan recovers it
|
|
146
152
|
if (!existsSync(targetPath)) this.writeWhole(session);
|
|
153
|
+
|
|
147
154
|
// forget the in-memory tracking migration seeded: the threads scan is the one recovery path
|
|
148
155
|
this.files.delete(session.id);
|
|
149
156
|
this.lineCounts.delete(session.id);
|
|
@@ -192,6 +199,7 @@ export class ThreadStore implements ThreadRepository {
|
|
|
192
199
|
|
|
193
200
|
return;
|
|
194
201
|
}
|
|
202
|
+
|
|
195
203
|
appendFileSync(filePath, JSON.stringify(session) + "\n");
|
|
196
204
|
this.lineCounts.set(session.id, lines + 1);
|
|
197
205
|
this.sessions.set(session.id, session);
|
|
@@ -199,9 +207,11 @@ export class ThreadStore implements ThreadRepository {
|
|
|
199
207
|
|
|
200
208
|
delete(id: string): boolean {
|
|
201
209
|
if (!this.sessions.delete(id)) return false;
|
|
210
|
+
|
|
202
211
|
const filePath = this.files.get(id);
|
|
203
212
|
|
|
204
213
|
if (filePath) rmSync(filePath, { force: true });
|
|
214
|
+
|
|
205
215
|
this.files.delete(id);
|
|
206
216
|
this.lineCounts.delete(id);
|
|
207
217
|
|
|
@@ -261,6 +271,7 @@ export class MemoryThreadStore implements ThreadRepository {
|
|
|
261
271
|
|
|
262
272
|
return;
|
|
263
273
|
}
|
|
274
|
+
|
|
264
275
|
const session = withHistory(parsed.value);
|
|
265
276
|
|
|
266
277
|
this.sessions.set(session.id, session);
|
|
@@ -79,6 +79,7 @@ const StoredAgentSchema = v.object({
|
|
|
79
79
|
messageId: v.optional(v.string()),
|
|
80
80
|
prompt: v.string(),
|
|
81
81
|
harnessPrompt: v.optional(v.string()),
|
|
82
|
+
attemptId: v.optional(v.string()),
|
|
82
83
|
quote: v.optional(v.string()),
|
|
83
84
|
context: v.optional(v.string()),
|
|
84
85
|
status: v.picklist(["queued", "running", "completed", "failed"]),
|
|
@@ -96,6 +97,25 @@ const StoredAgentSchema = v.object({
|
|
|
96
97
|
}),
|
|
97
98
|
),
|
|
98
99
|
),
|
|
100
|
+
continuation: v.optional(v.string()),
|
|
101
|
+
handoff: v.optional(
|
|
102
|
+
v.intersect([
|
|
103
|
+
v.object({
|
|
104
|
+
target: v.string(),
|
|
105
|
+
requestedTarget: v.optional(v.string()),
|
|
106
|
+
operationId: v.string(),
|
|
107
|
+
prompt: v.optional(v.string()),
|
|
108
|
+
source: v.optional(
|
|
109
|
+
v.object({ id: v.string(), label: v.string(), sessionId: v.optional(v.string()) }),
|
|
110
|
+
),
|
|
111
|
+
}),
|
|
112
|
+
v.variant("status", [
|
|
113
|
+
v.object({ status: v.literal("queued") }),
|
|
114
|
+
v.object({ status: v.literal("summarizing") }),
|
|
115
|
+
v.object({ status: v.literal("prepared"), text: v.string() }),
|
|
116
|
+
]),
|
|
117
|
+
]),
|
|
118
|
+
),
|
|
99
119
|
configOptions: v.optional(
|
|
100
120
|
v.array(
|
|
101
121
|
v.object({
|
package/src/thread-agent.test.ts
CHANGED
|
@@ -416,6 +416,7 @@ test("a shared plain input does not consume the owner's pending comments", () =>
|
|
|
416
416
|
const thread = createTestThread(home);
|
|
417
417
|
const adapter: AgentHarnessAdapter = {
|
|
418
418
|
id: "pi",
|
|
419
|
+
recovery: "durable",
|
|
419
420
|
label: "pi",
|
|
420
421
|
connect: () => ({
|
|
421
422
|
start: () => new Promise(() => {}),
|
|
@@ -474,12 +475,19 @@ test("Pi recovery keeps the frozen harness input and submission identity after i
|
|
|
474
475
|
let resumed = false;
|
|
475
476
|
const adapter: AgentHarnessAdapter = {
|
|
476
477
|
id: "pi",
|
|
478
|
+
recovery: "durable",
|
|
477
479
|
label: "pi",
|
|
478
480
|
connect: ({ onEvent }) => ({
|
|
479
481
|
start: async () => "durable-session",
|
|
480
482
|
prompt: async (text, id) => {
|
|
481
483
|
prompts.push({ text, id });
|
|
482
|
-
|
|
484
|
+
|
|
485
|
+
if (!resumed) {
|
|
486
|
+
onEvent({ kind: "message", id: "durable-answer", text: "Partial reply", replace: true });
|
|
487
|
+
|
|
488
|
+
return new Promise(() => {});
|
|
489
|
+
}
|
|
490
|
+
|
|
483
491
|
onEvent({ kind: "message", id: "durable-answer", text: "Recovered answer", replace: true });
|
|
484
492
|
|
|
485
493
|
return { outcome: "completed" as const };
|
|
@@ -515,6 +523,7 @@ test("Pi recovery keeps the frozen harness input and submission identity after i
|
|
|
515
523
|
await Bun.sleep(1);
|
|
516
524
|
expect(prompts).toHaveLength(2);
|
|
517
525
|
expect(prompts[1]).toEqual(prompts[0]);
|
|
526
|
+
expect(manager.get(thread.id).messages.at(-1)?.complete).toBe(true);
|
|
518
527
|
expect(manager.get(thread.id).messages.map((message) => message.text)).toEqual([
|
|
519
528
|
"Recover",
|
|
520
529
|
"Recovered answer",
|
|
@@ -545,6 +554,7 @@ test("explicit shared comments execute the frozen payload and retain the origina
|
|
|
545
554
|
enabled: true,
|
|
546
555
|
adapter: {
|
|
547
556
|
id: "pi",
|
|
557
|
+
recovery: "durable",
|
|
548
558
|
label: "pi",
|
|
549
559
|
connect: () => ({
|
|
550
560
|
start: async () => "session",
|
|
@@ -592,6 +602,7 @@ test("a failed connection finishes asynchronous close before the next queued tur
|
|
|
592
602
|
onChange() {},
|
|
593
603
|
adapter: {
|
|
594
604
|
id: "pi",
|
|
605
|
+
recovery: "durable",
|
|
595
606
|
label: "pi",
|
|
596
607
|
connect: () => {
|
|
597
608
|
const first = ++connects === 1;
|
|
@@ -650,6 +661,7 @@ test("shutdown joins a failed turn that is already retiring its durable writer",
|
|
|
650
661
|
onChange() {},
|
|
651
662
|
adapter: {
|
|
652
663
|
id: "pi",
|
|
664
|
+
recovery: "durable",
|
|
653
665
|
label: "pi",
|
|
654
666
|
connect: () => ({
|
|
655
667
|
start: async () => "session",
|
|
@@ -699,6 +711,7 @@ test("shutdown seals admission so a later configuration cannot open a new harnes
|
|
|
699
711
|
onChange() {},
|
|
700
712
|
adapter: {
|
|
701
713
|
id: "pi",
|
|
714
|
+
recovery: "durable",
|
|
702
715
|
label: "pi",
|
|
703
716
|
connect: () => {
|
|
704
717
|
connects++;
|