pi-harness-runtime 1.1.30 → 1.1.32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. package/harness/langchain/agents.ts +198 -0
  2. package/harness/langchain/checkpointer.test.ts +269 -0
  3. package/harness/langchain/checkpointer.ts +343 -0
  4. package/harness/langchain/daemon.test.ts +398 -0
  5. package/harness/langchain/daemon.ts +921 -0
  6. package/harness/langchain/graph.ts +291 -0
  7. package/harness/langchain/models.ts +95 -0
  8. package/harness/langchain/run.ts +207 -0
  9. package/harness/langchain/surge.test.ts +214 -0
  10. package/harness/langchain/surge.ts +217 -0
  11. package/package.json +8 -2
  12. package/packages/a2a-adapter/package.json +1 -1
  13. package/packages/architecture-generator/package.json +1 -1
  14. package/packages/auth/package.json +1 -1
  15. package/packages/autonomous-refactor/package.json +1 -1
  16. package/packages/autonomous-runtime/package.json +1 -1
  17. package/packages/cache-strategy/package.json +1 -1
  18. package/packages/capability-registry/package.json +1 -1
  19. package/packages/checkpoint/package.json +1 -1
  20. package/packages/cli-plugin-sdk/package.json +1 -1
  21. package/packages/clipboard/package.json +1 -1
  22. package/packages/clipboard-plugin/package.json +1 -1
  23. package/packages/code-generation/package.json +1 -1
  24. package/packages/code-review/package.json +1 -1
  25. package/packages/codex-adapter/package.json +1 -1
  26. package/packages/config-capture/package.json +1 -1
  27. package/packages/context-compiler/package.json +1 -1
  28. package/packages/context-discovery/package.json +1 -1
  29. package/packages/context-manager/package.json +1 -1
  30. package/packages/cookie-sanitizer/package.json +1 -1
  31. package/packages/cost-optimizer/package.json +1 -1
  32. package/packages/dependency-analyzer/package.json +1 -1
  33. package/packages/django-plugin/package.json +1 -1
  34. package/packages/doc-generator/package.json +1 -1
  35. package/packages/evaluation-engine/package.json +1 -1
  36. package/packages/evaluation-runner/package.json +1 -1
  37. package/packages/event-bus/dist/src/types.d.ts +1 -1
  38. package/packages/event-bus/dist/src/types.d.ts.map +1 -1
  39. package/packages/event-bus/dist/src/types.js.map +1 -1
  40. package/packages/event-bus/package.json +1 -1
  41. package/packages/event-bus/src/types.ts +1 -0
  42. package/packages/event-store/package.json +1 -1
  43. package/packages/experience-replay/package.json +1 -1
  44. package/packages/feedback-collector/package.json +1 -1
  45. package/packages/file-copy-helper/package.json +1 -1
  46. package/packages/framework-detector/package.json +1 -1
  47. package/packages/framework-plugin-sdk/package.json +1 -1
  48. package/packages/frappe-plugin/package.json +1 -1
  49. package/packages/generic-web-plugin/package.json +1 -1
  50. package/packages/health-monitor/package.json +1 -1
  51. package/packages/intent-analyzer/package.json +1 -1
  52. package/packages/knowledge-graph/package.json +1 -1
  53. package/packages/knowledge-retrieval/package.json +1 -1
  54. package/packages/laravel-plugin/package.json +1 -1
  55. package/packages/learning-engine/package.json +1 -1
  56. package/packages/mcp-adapter/package.json +1 -1
  57. package/packages/memory-engine/package.json +1 -1
  58. package/packages/milestone-manager/package.json +1 -1
  59. package/packages/model-registry/package.json +1 -1
  60. package/packages/nextjs-plugin/package.json +1 -1
  61. package/packages/notification/package.json +1 -1
  62. package/packages/observability/package.json +1 -1
  63. package/packages/okf-indexer/package.json +1 -1
  64. package/packages/performance-optimizer/package.json +1 -1
  65. package/packages/privilege-broker/package.json +1 -1
  66. package/packages/project-analyzer/package.json +1 -1
  67. package/packages/project-bootstrap/package.json +1 -1
  68. package/packages/projection-engine/package.json +1 -1
  69. package/packages/prompt-compiler/package.json +1 -1
  70. package/packages/prompt-versioning/package.json +1 -1
  71. package/packages/provider-adapter-sdk/package.json +1 -1
  72. package/packages/provider-router/package.json +1 -1
  73. package/packages/provider-selector/package.json +1 -1
  74. package/packages/providers/package.json +1 -1
  75. package/packages/quota-manager/package.json +1 -1
  76. package/packages/rate-limiter/package.json +1 -1
  77. package/packages/react-vite-plugin/package.json +1 -1
  78. package/packages/release-manager/package.json +1 -1
  79. package/packages/requirement-compiler/package.json +1 -1
  80. package/packages/runtime/package.json +1 -1
  81. package/packages/scheduler/package.json +1 -1
  82. package/packages/scheduler-adapter/package.json +1 -1
  83. package/packages/session/package.json +1 -1
  84. package/packages/session-api/package.json +1 -1
  85. package/packages/session-export/package.json +1 -1
  86. package/packages/shared-context/package.json +1 -1
  87. package/packages/skill-mcp-client/package.json +1 -1
  88. package/packages/skill-registry/package.json +1 -1
  89. package/packages/sprint-planner/package.json +1 -1
  90. package/packages/subscription-engine/package.json +1 -1
  91. package/packages/task-compiler/package.json +1 -1
  92. package/packages/tencentdb-memory/package.json +1 -1
  93. package/packages/tencentdb-sync/package.json +1 -1
  94. package/packages/test-data-generator/package.json +1 -1
  95. package/packages/test-generator/package.json +1 -1
  96. package/packages/todo-bd-sync/package.json +1 -1
  97. package/packages/token-estimation/package.json +1 -1
  98. package/packages/token-optimizer/package.json +1 -1
  99. package/packages/tui/package.json +1 -1
  100. package/packages/types/package.json +1 -1
  101. package/packages/workflow-events/package.json +1 -1
  102. package/packages/workspace-scanner/package.json +1 -1
  103. package/packages/worktree/package.json +1 -1
  104. package/packages/write-review/package.json +1 -1
@@ -0,0 +1,343 @@
1
+ /**
2
+ * File-based checkpointer — persists LangGraph StateGraph checkpoints to disk.
3
+ *
4
+ * Enables crash-safe loop resume: if the daemon dies mid-task, a new instance
5
+ * resumes from the last checkpoint instead of restarting from scratch.
6
+ *
7
+ * Storage layout:
8
+ * <root>/
9
+ * ├── <threadId>/
10
+ * │ ├── index.jsonl # one line per checkpoint (id, ts, parentId, step)
11
+ * │ └── <checkpointId>.json # full checkpoint + metadata + parentConfig
12
+ *
13
+ * Wiki: wiki/auto-trigger-multi-agent.md §M5
14
+ */
15
+
16
+ import {
17
+ existsSync,
18
+ mkdirSync,
19
+ readFileSync,
20
+ readdirSync,
21
+ renameSync,
22
+ unlinkSync,
23
+ writeFileSync,
24
+ } from "node:fs";
25
+ import { join } from "node:path";
26
+
27
+ import {
28
+ BaseCheckpointSaver,
29
+ Checkpoint,
30
+ CheckpointListOptions,
31
+ CheckpointMetadata,
32
+ CheckpointTuple,
33
+ } from "@langchain/langgraph-checkpoint";
34
+ import type { ChannelVersions } from "@langchain/langgraph-checkpoint";
35
+ import type { RunnableConfig } from "@langchain/core/runnables";
36
+
37
+ // ─── Types ───────────────────────────────────────────────────────────────────
38
+
39
+ /** Minimal JSON serializer compatible with SerializerProtocol. */
40
+ const jsonSerializer = {
41
+ async dumpsTyped(data: unknown): Promise<[string, Uint8Array]> {
42
+ return ["application/json", new TextEncoder().encode(JSON.stringify(data))];
43
+ },
44
+ async loadsTyped(
45
+ _type: string,
46
+ data: Uint8Array | string,
47
+ ): Promise<unknown> {
48
+ try {
49
+ const bytes: Uint8Array =
50
+ typeof data === "string" ? new TextEncoder().encode(data) : data;
51
+ return JSON.parse(new TextDecoder().decode(bytes));
52
+ } catch {
53
+ throw new Error("Failed to deserialize checkpoint data");
54
+ }
55
+ },
56
+ } as const;
57
+
58
+ // ─── Storage helpers ────────────────────────────────────────────────────────
59
+
60
+ interface IndexEntry {
61
+ id: string;
62
+ ts: string;
63
+ parentId?: string;
64
+ step: number;
65
+ }
66
+
67
+ function threadDir(root: string, threadId: string): string {
68
+ return join(root, `thread-${threadId}`);
69
+ }
70
+
71
+ function checkpointPath(threadDir: string, checkpointId: string): string {
72
+ return join(threadDir, `${checkpointId}.json`);
73
+ }
74
+
75
+ function indexPath(threadDir: string): string {
76
+ return join(threadDir, "index.jsonl");
77
+ }
78
+
79
+ function ensureThreadDir(root: string, threadId: string): string {
80
+ const dir = threadDir(root, threadId);
81
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
82
+ return dir;
83
+ }
84
+
85
+ function readIndex(threadDir: string): IndexEntry[] {
86
+ const path = indexPath(threadDir);
87
+ if (!existsSync(path)) return [];
88
+ try {
89
+ return readFileSync(path, "utf8")
90
+ .split("\n")
91
+ .filter((l) => l.trim())
92
+ .map((l) => JSON.parse(l) as IndexEntry);
93
+ } catch {
94
+ return [];
95
+ }
96
+ }
97
+
98
+ function appendIndex(threadDir: string, entry: IndexEntry): void {
99
+ const path = indexPath(threadDir);
100
+ const line = JSON.stringify(entry) + "\n";
101
+ if (existsSync(path)) {
102
+ const fd = require("node:fs").openSync(path, "a");
103
+ require("node:fs").writeSync(fd, line);
104
+ require("node:fs").closeSync(fd);
105
+ } else {
106
+ writeFileSync(path, line, "utf8");
107
+ }
108
+ }
109
+
110
+ function atomicWrite(path: string, content: string): void {
111
+ const tmp = `${path}.tmp`;
112
+ writeFileSync(tmp, content, "utf8");
113
+ renameSync(tmp, path);
114
+ }
115
+
116
+ // ─── FileCheckpointSaver ──────────────────────────────────────────────────────
117
+
118
+ /**
119
+ * File-based LangGraph checkpointer. Implements BaseCheckpointSaver so it can be
120
+ * passed directly to `buildWriteReviewLoop()` as the `checkpointer` option.
121
+ *
122
+ * Usage:
123
+ * const saver = new FileCheckpointSaver({ root: "/tmp/checkpoints" });
124
+ * const loop = buildWriteReviewLoop(deps, { checkpointer: saver });
125
+ *
126
+ * On daemon restart, pass the same root — checkpoints persist across restarts.
127
+ */
128
+ export class FileCheckpointSaver extends BaseCheckpointSaver {
129
+ private readonly root: string;
130
+
131
+ constructor(opts: { root: string }) {
132
+ super(jsonSerializer);
133
+ this.root = opts.root;
134
+ }
135
+
136
+ // ─── BaseCheckpointSaver ─────────────────────────────────────────────
137
+
138
+ async get(config: RunnableConfig): Promise<Checkpoint | undefined> {
139
+ const tuple = await this.getTuple(config);
140
+ return tuple?.checkpoint;
141
+ }
142
+
143
+ async getTuple(
144
+ config: RunnableConfig,
145
+ ): Promise<CheckpointTuple | undefined> {
146
+ const threadId = String(config.configurable?.thread_id ?? "");
147
+ if (!threadId) return undefined;
148
+
149
+ const dir = threadDir(this.root, threadId);
150
+ if (!existsSync(dir)) return undefined;
151
+
152
+ const checkpointId = String(config.configurable?.checkpoint_id ?? "");
153
+ if (!checkpointId) {
154
+ const index = readIndex(dir);
155
+ if (index.length === 0) return undefined;
156
+ return this._loadCheckpoint(config, dir, index[index.length - 1].id);
157
+ }
158
+
159
+ return this._loadCheckpoint(config, dir, checkpointId);
160
+ }
161
+
162
+ private async _loadCheckpoint(
163
+ config: RunnableConfig,
164
+ dir: string,
165
+ checkpointId: string,
166
+ ): Promise<CheckpointTuple | undefined> {
167
+ const path = checkpointPath(dir, checkpointId);
168
+ if (!existsSync(path)) return undefined;
169
+
170
+ try {
171
+ const raw = readFileSync(path);
172
+ const data = JSON.parse(
173
+ new TextDecoder().decode(raw),
174
+ ) as {
175
+ checkpoint: Checkpoint;
176
+ metadata: CheckpointMetadata;
177
+ parentConfig?: RunnableConfig;
178
+ };
179
+
180
+ const index = readIndex(dir);
181
+ const entry = index.find((e) => e.id === checkpointId);
182
+ const parentId = entry?.parentId;
183
+
184
+ let parentConfig: RunnableConfig | undefined;
185
+ if (
186
+ parentId &&
187
+ existsSync(checkpointPath(dir, parentId))
188
+ ) {
189
+ parentConfig = {
190
+ configurable: {
191
+ thread_id: config.configurable?.thread_id,
192
+ checkpoint_id: parentId,
193
+ },
194
+ };
195
+ }
196
+
197
+ return {
198
+ config,
199
+ checkpoint: data.checkpoint,
200
+ metadata: data.metadata,
201
+ parentConfig: parentConfig ?? data.parentConfig,
202
+ };
203
+ } catch {
204
+ return undefined;
205
+ }
206
+ }
207
+
208
+ async *list(
209
+ config: RunnableConfig,
210
+ options?: CheckpointListOptions,
211
+ ): AsyncGenerator<CheckpointTuple> {
212
+ const threadId = String(config.configurable?.thread_id ?? "");
213
+ if (!threadId) return;
214
+
215
+ const dir = threadDir(this.root, threadId);
216
+ if (!existsSync(dir)) return;
217
+
218
+ const index = [...readIndex(dir)].sort((a, b) =>
219
+ b.ts.localeCompare(a.ts),
220
+ );
221
+
222
+ const limit = options?.limit ?? 100;
223
+ let count = 0;
224
+
225
+ for (const entry of index) {
226
+ if (count >= limit) break;
227
+ const tuple = await this._loadCheckpoint(config, dir, entry.id);
228
+ if (tuple) yield tuple;
229
+ count++;
230
+ }
231
+ }
232
+
233
+ async put(
234
+ config: RunnableConfig,
235
+ checkpoint: Checkpoint,
236
+ metadata: CheckpointMetadata,
237
+ _newVersions: ChannelVersions,
238
+ ): Promise<RunnableConfig> {
239
+ const threadId = String(config.configurable?.thread_id ?? "");
240
+ if (!threadId) throw new Error("thread_id is required for checkpointing");
241
+
242
+ const dir = ensureThreadDir(this.root, threadId);
243
+ const parentId = (metadata.parents as Record<string, string | undefined>)?.[""];
244
+
245
+ const path = checkpointPath(dir, checkpoint.id);
246
+ atomicWrite(
247
+ path,
248
+ JSON.stringify({
249
+ checkpoint,
250
+ metadata,
251
+ parentConfig: parentId
252
+ ? { configurable: { thread_id: threadId, checkpoint_id: parentId } }
253
+ : undefined,
254
+ }),
255
+ );
256
+
257
+ appendIndex(dir, {
258
+ id: checkpoint.id,
259
+ ts: checkpoint.ts,
260
+ parentId,
261
+ step: metadata.step,
262
+ });
263
+
264
+ return {
265
+ configurable: { ...config.configurable, checkpoint_id: checkpoint.id },
266
+ };
267
+ }
268
+
269
+ async putWrites(
270
+ _config: RunnableConfig,
271
+ _writes: [],
272
+ _taskId: string,
273
+ ): Promise<void> {
274
+ // Pending writes stored inline in the checkpoint JSON — no separate file needed.
275
+ }
276
+
277
+ async deleteThread(threadId: string): Promise<void> {
278
+ const dir = threadDir(this.root, threadId);
279
+ if (!existsSync(dir)) return;
280
+
281
+ for (const file of readdirSync(dir)) {
282
+ try {
283
+ unlinkSync(join(dir, file));
284
+ } catch {
285
+ /* ignore */
286
+ }
287
+ }
288
+
289
+ try {
290
+ require("node:fs").rmdirSync(dir);
291
+ } catch {
292
+ /* ignore */
293
+ }
294
+ }
295
+
296
+ toJSON(): string {
297
+ return "FileCheckpointSaver";
298
+ }
299
+
300
+ // ─── Inherited defaults from BaseCheckpointSaver ───────────────────────
301
+
302
+ // getDeltaChannelHistory: uses the default implementation (walks getTuple chain)
303
+ // getNextVersion: uses the default integer implementation
304
+
305
+ // ─── Helpers ───────────────────────────────────────────────────────
306
+
307
+ /** List all known thread IDs with saved checkpoints. */
308
+ listThreads(): string[] {
309
+ if (!existsSync(this.root)) return [];
310
+ return readdirSync(this.root)
311
+ .filter((f) => f.startsWith("thread-"))
312
+ .map((f) => f.replace("thread-", ""));
313
+ }
314
+
315
+ /** Delete all checkpoints for all threads (cleanup). */
316
+ clearAll(): void {
317
+ if (!existsSync(this.root)) return;
318
+ for (const thread of readdirSync(this.root)) {
319
+ const fullPath = join(this.root, thread);
320
+ if (!existsSync(fullPath)) continue;
321
+ for (const file of readdirSync(fullPath)) {
322
+ try {
323
+ unlinkSync(join(fullPath, file));
324
+ } catch {
325
+ /* ignore */
326
+ }
327
+ }
328
+ try {
329
+ require("node:fs").rmdirSync(fullPath);
330
+ } catch {
331
+ /* ignore */
332
+ }
333
+ }
334
+ }
335
+ }
336
+
337
+ // ─── Factory ────────────────────────────────────────────────────────────────
338
+
339
+ /** Build a FileCheckpointSaver wired to the daemon's workspace. */
340
+ export function createLoopCheckpointer(workspace: string): FileCheckpointSaver {
341
+ const root = join(workspace, ".checkpoints");
342
+ return new FileCheckpointSaver({ root });
343
+ }