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,198 @@
1
+ /**
2
+ * Multi-Agent definitions — GPT (planner/reviewer/supervisor) + MiniMax (coder).
3
+ *
4
+ * This is a TypeScript port of the "supervisor + agents-as-tools" pattern from
5
+ * Mohamad-Hachem/MultiAgent_Wedding_Planner_With_Langchain (agents.py):
6
+ *
7
+ * Python (agents.py) → TypeScript (this file)
8
+ * ------------------------------------------------------------------
9
+ * subagent1 = create_agent(...) → coderAgent = createAgent({ model: minimax ... })
10
+ * @tool delegate_to_subagent1 → delegateToCoder = tool(... { name: "delegate_to_coder" })
11
+ * main_agent(tools=[delegates]) → supervisor = createAgent({ tools: [delegateToCoder, ...] })
12
+ *
13
+ * Verdict values intentionally match the existing harness LoopVerdict type
14
+ * ("approved" | "changes_requested" | "blocked") so this module can feed the
15
+ * existing blackboard / HerdrEventBus infrastructure.
16
+ *
17
+ * Wiki: wiki/multi-agent-langchain.md
18
+ */
19
+
20
+ import { createAgent, tool } from "langchain";
21
+ import { z } from "zod";
22
+ import {
23
+ createCoderModel,
24
+ createPlannerModel,
25
+ createReviewerModel,
26
+ type ModelOptions,
27
+ } from "./models.js";
28
+
29
+ // ─── Structured review output ───────────────────────────────────────────────
30
+
31
+ export const ReviewVerdictSchema = z.object({
32
+ verdict: z
33
+ .enum(["approved", "changes_requested", "blocked"])
34
+ .describe("Overall review verdict"),
35
+ summary: z.string().describe("One-paragraph summary of the review"),
36
+ comments: z
37
+ .array(
38
+ z.object({
39
+ file: z.string().optional().describe("Affected file, if known"),
40
+ comment: z.string().describe("Actionable change request"),
41
+ severity: z.enum(["critical", "major", "minor"]).optional(),
42
+ }),
43
+ )
44
+ .describe("Comments to send back to the coder (empty when approved)"),
45
+ });
46
+
47
+ export type ReviewVerdict = z.infer<typeof ReviewVerdictSchema>;
48
+
49
+ // ─── Prompts ────────────────────────────────────────────────────────────────
50
+
51
+ const PLANNER_PROMPT = `You are the Planning Agent (GPT). Given a feature request, produce a
52
+ concise implementation plan: goals, non-goals, ordered steps, files to touch,
53
+ risks, and a definition-of-done the reviewer can check against.
54
+ Output markdown only.`;
55
+
56
+ const CODER_PROMPT = `You are the Coding Agent (MiniMax). You receive a plan (and possibly
57
+ review comments from a previous iteration) and produce complete, runnable code.
58
+ Always output fenced code blocks with file-path headers. Address every review
59
+ comment explicitly.`;
60
+
61
+ const REVIEWER_PROMPT = `You are the Code Review Agent (GPT). You review the coder's output
62
+ against the plan and the review checklist. Be strict but fair: request changes
63
+ only for real problems. Respond via the structured schema.`;
64
+
65
+ const SUPERVISOR_PROMPT = `You are the Supervisor (GPT). Coordinate the workflow:
66
+ 1. Call delegate_to_coder with the instruction (include review comments when fixing).
67
+ 2. Call delegate_to_reviewer with the plan + code to review.
68
+ 3. If the reviewer requests changes, call delegate_to_coder again with the comments.
69
+ 4. Repeat until approved or you judge further iterations unproductive (then stop and summarize).
70
+ Keep the loop tight: no more than 4 coder-review rounds unless the task clearly needs more.`;
71
+
72
+ // ─── Agents ─────────────────────────────────────────────────────────────────
73
+
74
+ export function createPlannerAgent(opts: ModelOptions = {}) {
75
+ return createAgent({
76
+ model: createPlannerModel(opts),
77
+ tools: [],
78
+ systemPrompt: PLANNER_PROMPT,
79
+ });
80
+ }
81
+
82
+ export function createCoderAgent(opts: ModelOptions = {}) {
83
+ return createAgent({
84
+ model: createCoderModel(opts),
85
+ tools: [], // roadmap: file read/write tools for real repo edits
86
+ systemPrompt: CODER_PROMPT,
87
+ });
88
+ }
89
+
90
+ export function createReviewerAgent(opts: ModelOptions = {}) {
91
+ return createAgent({
92
+ model: createReviewerModel(opts),
93
+ tools: [],
94
+ systemPrompt: REVIEWER_PROMPT,
95
+ responseFormat: ReviewVerdictSchema,
96
+ });
97
+ }
98
+
99
+ export type PlannerAgent = ReturnType<typeof createPlannerAgent>;
100
+ export type CoderAgent = ReturnType<typeof createCoderAgent>;
101
+ export type ReviewerAgent = ReturnType<typeof createReviewerAgent>;
102
+ export type SupervisorAgent = ReturnType<typeof createSupervisor>;
103
+
104
+ /** Extract the last assistant message text from an agent result. */
105
+ export function lastMessage(agentResult: {
106
+ messages: Array<{ content: unknown }>;
107
+ }): string {
108
+ const last = agentResult.messages[agentResult.messages.length - 1];
109
+ if (typeof last?.content === "string") return last.content;
110
+ if (Array.isArray(last?.content)) {
111
+ return last.content.map((c: { text?: string }) => c?.text ?? "").join("\n");
112
+ }
113
+ return "";
114
+ }
115
+
116
+ // ─── Delegation tools (agents-as-tools, port of agents.py) ──────────────────
117
+
118
+ export function buildDelegationTools(
119
+ opts: { coder?: CoderAgent; reviewer?: ReviewerAgent } = {},
120
+ ) {
121
+ const coder = opts.coder ?? createCoderAgent();
122
+ const reviewer = opts.reviewer ?? createReviewerAgent();
123
+
124
+ const delegateToCoder = tool(
125
+ async ({ instruction, reviewComments }) => {
126
+ const userMsg = reviewComments
127
+ ? `${instruction}\n\nPrevious review requested changes:\n${reviewComments}`
128
+ : instruction;
129
+ const result = await coder.invoke({
130
+ messages: [{ role: "user", content: userMsg }],
131
+ });
132
+ return lastMessage(result);
133
+ },
134
+ {
135
+ name: "delegate_to_coder",
136
+ description:
137
+ "Delegate a coding instruction to the MiniMax coder agent. " +
138
+ "Pass previous review comments when fixing issues.",
139
+ schema: z.object({
140
+ instruction: z.string().describe("What the coder should implement or fix"),
141
+ reviewComments: z
142
+ .string()
143
+ .optional()
144
+ .describe("Review comments from the previous iteration"),
145
+ }),
146
+ },
147
+ );
148
+
149
+ const delegateToReviewer = tool(
150
+ async ({ plan, code }) => {
151
+ const result = await reviewer.invoke({
152
+ messages: [
153
+ {
154
+ role: "user",
155
+ content: `## Plan\n${plan}\n\n## Code to review\n${code}`,
156
+ },
157
+ ],
158
+ });
159
+ const structured = (result as { structuredResponse?: ReviewVerdict })
160
+ .structuredResponse;
161
+ return structured
162
+ ? JSON.stringify(structured, null, 2)
163
+ : lastMessage(result);
164
+ },
165
+ {
166
+ name: "delegate_to_reviewer",
167
+ description:
168
+ "Delegate a code review to the GPT reviewer agent. Returns a JSON verdict: " +
169
+ '{verdict: "approved"|"changes_requested"|"blocked", summary, comments[]}.',
170
+ schema: z.object({
171
+ plan: z.string().describe("The implementation plan"),
172
+ code: z.string().describe("The code the coder produced"),
173
+ }),
174
+ },
175
+ );
176
+
177
+ return { delegateToCoder, delegateToReviewer };
178
+ }
179
+
180
+ // ─── Supervisor (dynamic routing variant) ───────────────────────────────────
181
+
182
+ export function createSupervisor(
183
+ opts: {
184
+ plannerModel?: ModelOptions;
185
+ coder?: CoderAgent;
186
+ reviewer?: ReviewerAgent;
187
+ } = {},
188
+ ) {
189
+ const { delegateToCoder, delegateToReviewer } = buildDelegationTools({
190
+ coder: opts.coder,
191
+ reviewer: opts.reviewer,
192
+ });
193
+ return createAgent({
194
+ model: createPlannerModel(opts.plannerModel ?? {}),
195
+ tools: [delegateToCoder, delegateToReviewer],
196
+ systemPrompt: SUPERVISOR_PROMPT,
197
+ });
198
+ }
@@ -0,0 +1,269 @@
1
+ /**
2
+ * FileCheckpointSaver tests — T9–T11 (wiki/auto-trigger-multi-agent.md §M5)
3
+ *
4
+ * Run:
5
+ * bun test harness/langchain/checkpointer.test.ts
6
+ */
7
+
8
+ import { describe, it } from "node:test";
9
+ import assert from "node:assert";
10
+ import { existsSync, mkdirSync, rmSync } from "node:fs";
11
+ import { join } from "node:path";
12
+ import { randomUUID } from "node:crypto";
13
+
14
+ import {
15
+ FileCheckpointSaver,
16
+ createLoopCheckpointer,
17
+ } from "./checkpointer.js";
18
+
19
+ // ─── Fixtures ────────────────────────────────────────────────────────────────
20
+
21
+ const ROOT = `/tmp/checkpointer-test-${randomUUID().slice(0, 8)}`;
22
+
23
+ function freshSaver(): FileCheckpointSaver {
24
+ const saver = new FileCheckpointSaver({ root: ROOT });
25
+ saver.clearAll();
26
+ return saver;
27
+ }
28
+
29
+ const THREAD = "thread-abc123";
30
+ const CONFIG = { configurable: { thread_id: THREAD } };
31
+ const CONFIG_WITH_CPID = {
32
+ configurable: { thread_id: THREAD, checkpoint_id: "cp-001" },
33
+ };
34
+
35
+ // ─── T9: Basic put/get ──────────────────────────────────────────────────────
36
+
37
+ describe("T9 — put and get basic checkpoint", () => {
38
+ it("stores and retrieves a checkpoint", async () => {
39
+ const saver = freshSaver();
40
+
41
+ const checkpoint = {
42
+ v: 4,
43
+ id: "cp-001",
44
+ ts: "2026-08-31T10:00:00.000Z",
45
+ channel_values: { plan: "do the thing", code: "" },
46
+ channel_versions: { plan: 1, code: 0 },
47
+ versions_seen: {},
48
+ };
49
+ const metadata = {
50
+ source: "input" as const,
51
+ step: -1,
52
+ parents: {},
53
+ };
54
+
55
+ const updatedConfig = await saver.put(CONFIG, checkpoint, metadata, {});
56
+ assert.equal(
57
+ updatedConfig.configurable?.checkpoint_id,
58
+ "cp-001",
59
+ );
60
+
61
+ // Retrieve it
62
+ const tuple = await saver.getTuple(CONFIG_WITH_CPID);
63
+ assert.ok(tuple, "should retrieve the checkpoint");
64
+ assert.equal(tuple?.checkpoint.id, "cp-001");
65
+ assert.equal(
66
+ (tuple?.checkpoint.channel_values as Record<string, unknown>)?.plan,
67
+ "do the thing",
68
+ );
69
+ });
70
+
71
+ it("get() returns the checkpoint", async () => {
72
+ const saver = freshSaver();
73
+ const cp = {
74
+ v: 4,
75
+ id: "cp-get",
76
+ ts: "2026-08-31T10:01:00.000Z",
77
+ channel_values: { plan: "test plan" },
78
+ channel_versions: {},
79
+ versions_seen: {},
80
+ };
81
+ await saver.put(CONFIG, cp, { source: "input", step: -1, parents: {} }, {});
82
+
83
+ const retrieved = await saver.get({
84
+ configurable: { thread_id: THREAD, checkpoint_id: "cp-get" },
85
+ });
86
+ assert.ok(retrieved, "should return the checkpoint");
87
+ assert.equal(retrieved?.id, "cp-get");
88
+ });
89
+ });
90
+
91
+ // ─── T10: Parent chain ──────────────────────────────────────────────────────
92
+
93
+ describe("T10 — parent chain", () => {
94
+ it("stores and retrieves a parent chain (resume)", async () => {
95
+ const saver = freshSaver();
96
+
97
+ const root = {
98
+ v: 4,
99
+ id: "root",
100
+ ts: "2026-08-31T10:00:00.000Z",
101
+ channel_values: {},
102
+ channel_versions: {},
103
+ versions_seen: {},
104
+ };
105
+ const child = {
106
+ v: 4,
107
+ id: "child",
108
+ ts: "2026-08-31T10:01:00.000Z",
109
+ channel_values: { plan: "the plan" },
110
+ channel_versions: { plan: 1 },
111
+ versions_seen: {},
112
+ };
113
+
114
+ await saver.put(
115
+ CONFIG,
116
+ root,
117
+ { source: "input", step: -1, parents: {} },
118
+ {},
119
+ );
120
+ await saver.put(
121
+ CONFIG,
122
+ child,
123
+ { source: "loop", step: 0, parents: { "": "root" } },
124
+ {},
125
+ );
126
+
127
+ // Retrieve child and verify parent
128
+ const tuple = await saver.getTuple({
129
+ configurable: { thread_id: THREAD, checkpoint_id: "child" },
130
+ });
131
+ assert.ok(tuple);
132
+ assert.ok(tuple?.parentConfig);
133
+ assert.equal(
134
+ tuple?.parentConfig?.configurable?.checkpoint_id,
135
+ "root",
136
+ );
137
+
138
+ // getTuple without checkpoint_id returns the latest
139
+ const latest = await saver.getTuple(CONFIG);
140
+ assert.ok(latest);
141
+ assert.equal(latest?.checkpoint.id, "child");
142
+ });
143
+ });
144
+
145
+ // ─── T11: list + deleteThread ───────────────────────────────────────────────
146
+
147
+ describe("T11 — list and deleteThread", () => {
148
+ it("lists checkpoints in reverse chronological order", async () => {
149
+ const saver = freshSaver();
150
+
151
+ for (let i = 0; i < 5; i++) {
152
+ const cp = {
153
+ v: 4,
154
+ id: `cp-${i}`,
155
+ ts: new Date(1000 + i * 1000).toISOString(),
156
+ channel_values: { step: i },
157
+ channel_versions: {},
158
+ versions_seen: {},
159
+ };
160
+ await saver.put(
161
+ CONFIG,
162
+ cp,
163
+ { source: "loop", step: i, parents: {} },
164
+ {},
165
+ );
166
+ }
167
+
168
+ const all: string[] = [];
169
+ for await (const tuple of saver.list(CONFIG)) {
170
+ all.push(tuple.checkpoint.id);
171
+ }
172
+ assert.equal(all.length, 5);
173
+ // Newest first
174
+ assert.equal(all[0], "cp-4");
175
+ assert.equal(all[4], "cp-0");
176
+ });
177
+
178
+ it("limits list results", async () => {
179
+ const saver = freshSaver();
180
+ for (let i = 0; i < 3; i++) {
181
+ const cp = {
182
+ v: 4,
183
+ id: `limit-${i}`,
184
+ ts: new Date(2000 + i * 1000).toISOString(),
185
+ channel_values: {},
186
+ channel_versions: {},
187
+ versions_seen: {},
188
+ };
189
+ await saver.put(
190
+ CONFIG,
191
+ cp,
192
+ { source: "loop", step: i, parents: {} },
193
+ {},
194
+ );
195
+ }
196
+
197
+ const limited: string[] = [];
198
+ for await (const tuple of saver.list(CONFIG, { limit: 2 })) {
199
+ limited.push(tuple.checkpoint.id);
200
+ }
201
+ assert.equal(limited.length, 2);
202
+ });
203
+
204
+ it("deleteThread removes all checkpoints for a thread", async () => {
205
+ const saver = freshSaver();
206
+
207
+ const cp = {
208
+ v: 4,
209
+ id: "delete-me",
210
+ ts: "2026-08-31T12:00:00.000Z",
211
+ channel_values: {},
212
+ channel_versions: {},
213
+ versions_seen: {},
214
+ };
215
+ await saver.put(
216
+ CONFIG,
217
+ cp,
218
+ { source: "input", step: -1, parents: {} },
219
+ {},
220
+ );
221
+
222
+ assert.ok(await saver.getTuple(CONFIG), "checkpoint should exist");
223
+
224
+ await saver.deleteThread(THREAD);
225
+
226
+ assert.ok(
227
+ !(await saver.getTuple(CONFIG)),
228
+ "checkpoint should be gone after deleteThread",
229
+ );
230
+ });
231
+
232
+ it("toJSON returns a string (not the internal object)", async () => {
233
+ const saver = freshSaver();
234
+ const json = saver.toJSON();
235
+ assert.equal(typeof json, "string");
236
+ assert.ok(json.length > 0);
237
+ });
238
+
239
+ it("listThreads returns thread ids", async () => {
240
+ const saver = freshSaver();
241
+ const tid = `thread-${randomUUID().slice(0, 6)}`;
242
+ const cp = {
243
+ v: 4,
244
+ id: "cp-t",
245
+ ts: "2026-08-31T12:00:00.000Z",
246
+ channel_values: {},
247
+ channel_versions: {},
248
+ versions_seen: {},
249
+ };
250
+ await saver.put(
251
+ { configurable: { thread_id: tid } },
252
+ cp,
253
+ { source: "input", step: -1, parents: {} },
254
+ {},
255
+ );
256
+
257
+ const threads = saver.listThreads();
258
+ assert.ok(threads.includes(tid), `Expected ${tid} in ${JSON.stringify(threads)}`);
259
+ });
260
+ });
261
+
262
+ // ─── T12: createLoopCheckpointer factory ─────────────────────────────────────
263
+
264
+ describe("T12 — createLoopCheckpointer factory", () => {
265
+ it("creates a saver with the correct root path", () => {
266
+ const saver = createLoopCheckpointer("/my/workspace");
267
+ assert.ok(saver instanceof FileCheckpointSaver);
268
+ });
269
+ });