pi-harness-runtime 1.1.50 → 1.1.77
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/CHANGELOG.md +30 -9
- package/README.md +119 -0
- package/harness/langchain/agents.d.ts +143 -0
- package/harness/langchain/agents.test.d.ts +2 -0
- package/harness/langchain/agents.ts +196 -13
- package/harness/langchain/checkpointer.d.ts +48 -0
- package/harness/langchain/checkpointer.test.d.ts +8 -0
- package/harness/langchain/cron-cli.d.ts +12 -0
- package/harness/langchain/cron-cli.ts +169 -0
- package/harness/langchain/daemon.d.ts +161 -0
- package/harness/langchain/daemon.test.d.ts +11 -0
- package/harness/langchain/daemon.test.ts +3 -1
- package/harness/langchain/daemon.ts +230 -12
- package/harness/langchain/file-tools.d.ts +97 -0
- package/harness/langchain/file-tools.ts +14 -6
- package/harness/langchain/graph.d.ts +706 -0
- package/harness/langchain/graph.ts +158 -25
- package/harness/langchain/model-router.d.ts +63 -0
- package/harness/langchain/model-router.test.d.ts +2 -0
- package/harness/langchain/model-router.test.ts +191 -0
- package/harness/langchain/model-router.ts +188 -0
- package/harness/langchain/models.d.ts +72 -0
- package/harness/langchain/models.ts +111 -49
- package/harness/langchain/ping-pong-middleware.d.ts +28 -0
- package/harness/langchain/ping-pong-middleware.test.d.ts +2 -0
- package/harness/langchain/ping-pong-middleware.test.ts +69 -0
- package/harness/langchain/ping-pong-middleware.ts +204 -0
- package/harness/langchain/run.d.ts +15 -0
- package/harness/langchain/run.ts +15 -3
- package/harness/langchain/scoreboard.test.d.ts +13 -0
- package/harness/langchain/scoreboard.test.ts +23 -1
- package/harness/langchain/status-line.d.ts +91 -0
- package/harness/langchain/status-line.test.d.ts +8 -0
- package/harness/langchain/surge.d.ts +103 -0
- package/harness/langchain/surge.test.d.ts +10 -0
- package/harness/langchain/surge.test.ts +179 -0
- package/harness/langchain/surge.ts +188 -19
- package/harness/langchain/widget.d.ts +105 -0
- package/harness/langchain/widget.test.d.ts +8 -0
- package/harness/ssh-detach-interceptor.test.ts +263 -0
- package/harness/ssh-detach-interceptor.ts +438 -0
- package/index.ts +212 -47
- package/package.json +4 -2
- package/packages/a2a-adapter/package.json +1 -1
- package/packages/architecture-generator/package.json +1 -1
- package/packages/auth/package.json +1 -1
- package/packages/autonomous-refactor/package.json +1 -1
- package/packages/autonomous-runtime/package.json +1 -1
- package/packages/cache-strategy/package.json +1 -1
- package/packages/capability-registry/package.json +1 -1
- package/packages/checkpoint/package.json +1 -1
- package/packages/cli-plugin-sdk/package.json +1 -1
- package/packages/clipboard/package.json +1 -1
- package/packages/clipboard-plugin/package.json +1 -1
- package/packages/code-generation/package.json +1 -1
- package/packages/code-review/package.json +1 -1
- package/packages/codex-adapter/package.json +1 -1
- package/packages/config-capture/package.json +1 -1
- package/packages/context-compiler/package.json +1 -1
- package/packages/context-discovery/package.json +1 -1
- package/packages/context-manager/package.json +1 -1
- package/packages/cookie-sanitizer/package.json +1 -1
- package/packages/cost-optimizer/package.json +1 -1
- package/packages/dependency-analyzer/package.json +1 -1
- package/packages/django-plugin/package.json +1 -1
- package/packages/doc-generator/package.json +1 -1
- package/packages/evaluation-engine/package.json +1 -1
- package/packages/evaluation-runner/package.json +1 -1
- package/packages/event-bus/package.json +1 -1
- package/packages/event-store/package.json +1 -1
- package/packages/experience-replay/package.json +1 -1
- package/packages/feedback-collector/package.json +1 -1
- package/packages/file-copy-helper/package.json +1 -1
- package/packages/framework-detector/package.json +1 -1
- package/packages/framework-plugin-sdk/package.json +1 -1
- package/packages/frappe-plugin/package.json +1 -1
- package/packages/generic-web-plugin/package.json +1 -1
- package/packages/health-monitor/package.json +1 -1
- package/packages/intent-analyzer/dist/src/index.d.ts +2 -0
- package/packages/intent-analyzer/dist/src/index.d.ts.map +1 -1
- package/packages/intent-analyzer/dist/src/index.js +1 -0
- package/packages/intent-analyzer/dist/src/index.js.map +1 -1
- package/packages/intent-analyzer/dist/src/ping-pong.d.ts +56 -0
- package/packages/intent-analyzer/dist/src/ping-pong.d.ts.map +1 -0
- package/packages/intent-analyzer/dist/src/ping-pong.js +286 -0
- package/packages/intent-analyzer/dist/src/ping-pong.js.map +1 -0
- package/packages/intent-analyzer/package.json +1 -1
- package/packages/intent-analyzer/src/index.ts +13 -0
- package/packages/intent-analyzer/src/ping-pong.ts +387 -0
- package/packages/intent-analyzer/test/ping-pong.test.ts +96 -0
- package/packages/jev-judge/README.md +234 -0
- package/packages/jev-judge/dist/decision-engine.d.ts +100 -0
- package/packages/jev-judge/dist/decision-engine.d.ts.map +1 -0
- package/packages/jev-judge/dist/decision-engine.js +151 -0
- package/packages/jev-judge/dist/decision-engine.js.map +1 -0
- package/packages/jev-judge/dist/e2e-judge.d.ts +61 -0
- package/packages/jev-judge/dist/e2e-judge.d.ts.map +1 -0
- package/packages/jev-judge/dist/e2e-judge.js +268 -0
- package/packages/jev-judge/dist/e2e-judge.js.map +1 -0
- package/packages/jev-judge/dist/flaky-detector.d.ts +34 -0
- package/packages/jev-judge/dist/flaky-detector.d.ts.map +1 -0
- package/packages/jev-judge/dist/flaky-detector.js +154 -0
- package/packages/jev-judge/dist/flaky-detector.js.map +1 -0
- package/packages/jev-judge/dist/index.d.ts +77 -0
- package/packages/jev-judge/dist/index.d.ts.map +1 -0
- package/packages/jev-judge/dist/index.js +271 -0
- package/packages/jev-judge/dist/index.js.map +1 -0
- package/packages/jev-judge/dist/types.d.ts +97 -0
- package/packages/jev-judge/dist/types.d.ts.map +1 -0
- package/packages/jev-judge/dist/types.js +8 -0
- package/packages/jev-judge/dist/types.js.map +1 -0
- package/packages/jev-judge/package.json +46 -0
- package/packages/jev-judge/src/decision-engine.ts +231 -0
- package/packages/jev-judge/src/e2e-judge.ts +364 -0
- package/packages/jev-judge/src/flaky-detector.ts +190 -0
- package/packages/jev-judge/src/index.ts +345 -0
- package/packages/jev-judge/src/types.ts +131 -0
- package/packages/knowledge-graph/package.json +1 -1
- package/packages/knowledge-retrieval/package.json +1 -1
- package/packages/laravel-plugin/package.json +1 -1
- package/packages/learning-engine/package.json +1 -1
- package/packages/mcp-adapter/package.json +1 -1
- package/packages/memory-engine/package.json +1 -1
- package/packages/milestone-manager/package.json +1 -1
- package/packages/model-registry/package.json +1 -1
- package/packages/nextjs-plugin/package.json +1 -1
- package/packages/notification/package.json +1 -1
- package/packages/observability/package.json +1 -1
- package/packages/okf-indexer/package.json +1 -1
- package/packages/performance-optimizer/package.json +1 -1
- package/packages/privilege-broker/package.json +1 -1
- package/packages/project-analyzer/package.json +1 -1
- package/packages/project-bootstrap/package.json +1 -1
- package/packages/projection-engine/package.json +1 -1
- package/packages/prompt-compiler/package.json +1 -1
- package/packages/prompt-versioning/package.json +1 -1
- package/packages/provider-adapter-sdk/package.json +1 -1
- package/packages/provider-router/package.json +1 -1
- package/packages/provider-selector/package.json +1 -1
- package/packages/providers/package.json +1 -1
- package/packages/quota-manager/package.json +1 -1
- package/packages/rate-limiter/package.json +1 -1
- package/packages/react-vite-plugin/package.json +1 -1
- package/packages/release-manager/package.json +1 -1
- package/packages/requirement-compiler/package.json +1 -1
- package/packages/runtime/package.json +1 -1
- package/packages/scheduler/package.json +1 -1
- package/packages/scheduler-adapter/package.json +1 -1
- package/packages/session/package.json +1 -1
- package/packages/session-api/package.json +1 -1
- package/packages/session-export/package.json +1 -1
- package/packages/shared-context/package.json +1 -1
- package/packages/skill-mcp-client/package.json +1 -1
- package/packages/skill-registry/package.json +1 -1
- package/packages/sprint-planner/package.json +1 -1
- package/packages/subscription-engine/package.json +1 -1
- package/packages/task-compiler/package.json +1 -1
- package/packages/tencentdb-memory/package.json +1 -1
- package/packages/tencentdb-sync/package.json +1 -1
- package/packages/test-data-generator/package.json +1 -1
- package/packages/test-generator/package.json +1 -1
- package/packages/todo-bd-sync/README.md +71 -0
- package/packages/todo-bd-sync/package.json +1 -1
- package/packages/todo-bd-sync/src/extension.ts +16 -1
- package/packages/token-estimation/package.json +1 -1
- package/packages/token-optimizer/package.json +1 -1
- package/packages/trajectory/dist/src/approved-patterns.d.ts +51 -0
- package/packages/trajectory/dist/src/approved-patterns.d.ts.map +1 -0
- package/packages/trajectory/dist/src/approved-patterns.js +134 -0
- package/packages/trajectory/dist/src/approved-patterns.js.map +1 -0
- package/packages/trajectory/dist/src/index.d.ts +13 -0
- package/packages/trajectory/dist/src/index.d.ts.map +1 -0
- package/packages/trajectory/dist/src/index.js +12 -0
- package/packages/trajectory/dist/src/index.js.map +1 -0
- package/packages/trajectory/dist/src/store.d.ts +69 -0
- package/packages/trajectory/dist/src/store.d.ts.map +1 -0
- package/packages/trajectory/dist/src/store.js +253 -0
- package/packages/trajectory/dist/src/store.js.map +1 -0
- package/packages/trajectory/dist/src/types.d.ts +73 -0
- package/packages/trajectory/dist/src/types.d.ts.map +1 -0
- package/packages/trajectory/dist/src/types.js +9 -0
- package/packages/trajectory/dist/src/types.js.map +1 -0
- package/packages/trajectory/dist/test/store.test.d.ts +2 -0
- package/packages/trajectory/dist/test/store.test.d.ts.map +1 -0
- package/packages/trajectory/dist/test/store.test.js +228 -0
- package/packages/trajectory/dist/test/store.test.js.map +1 -0
- package/packages/trajectory/package.json +29 -0
- package/packages/trajectory/src/approved-patterns.ts +180 -0
- package/packages/trajectory/src/index.ts +26 -0
- package/packages/trajectory/src/store.ts +285 -0
- package/packages/trajectory/src/types.ts +84 -0
- package/packages/trajectory/test/store.test.d.ts +2 -0
- package/packages/trajectory/test/store.test.ts +250 -0
- package/packages/tui/package.json +1 -1
- package/packages/types/package.json +1 -1
- package/packages/workflow-events/package.json +1 -1
- package/packages/workspace-scanner/package.json +1 -1
- package/packages/worktree/package.json +1 -1
- package/packages/write-review/package.json +1 -1
- package/packages/write-review/src/blackboard.ts +24 -1
- package/packages/write-review/src/types.ts +13 -0
- package/skills/harness-runtime/SKILL.md +67 -4
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Smart model router — reads the request prompt and decides which model to use
|
|
3
|
+
* for each agent role at runtime. No hardcoded model IDs; all choices are driven
|
|
4
|
+
* by env vars + optional per-request directives in the prompt.
|
|
5
|
+
*
|
|
6
|
+
* Per-request directives (optional — fallback to env defaults if absent):
|
|
7
|
+
*
|
|
8
|
+
* Planner directive:
|
|
9
|
+
* [planner: gpt-5.6] or [planner: GLM-5.2] or [planner: MiniMax-M2]
|
|
10
|
+
*
|
|
11
|
+
* Reviewer directive (master reviewer — GLM is the design default):
|
|
12
|
+
* [reviewer: GLM-5.2] or [reviewer: GPT-5.6]
|
|
13
|
+
*
|
|
14
|
+
* Coder directive:
|
|
15
|
+
* [coder: MiniMax-M2] or [coder: gpt-5.6]
|
|
16
|
+
*
|
|
17
|
+
* Examples in a prompt:
|
|
18
|
+
* "Implement X [reviewer: GPT-5.6] [planner: GLM-5.2]"
|
|
19
|
+
* "Use GLM as master reviewer for this task"
|
|
20
|
+
* "GPT as reviewer: please review this code"
|
|
21
|
+
*
|
|
22
|
+
* The parser is forgiving — these all work:
|
|
23
|
+
* [reviewer: GLM] → uses GLM_MODEL from env
|
|
24
|
+
* [reviewer: gpt] → uses PLANNER_MODEL from env (same family)
|
|
25
|
+
* [reviewer: MiniMax] → uses MINIMAX_MODEL from env
|
|
26
|
+
*
|
|
27
|
+
* Wiki: wiki/multi-agent-langchain.md
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
/** Matches [role: modelFamily] directives, case-insensitive. */
|
|
31
|
+
const DIRECTIVE_RE = /\[(\w+)\s*:\s*(\w[\w-]*)\]/gi;
|
|
32
|
+
|
|
33
|
+
/** Map from directive family name → env prefix. */
|
|
34
|
+
const FAMILY_MAP: Record<string, string> = {
|
|
35
|
+
gpt: "PLANNER",
|
|
36
|
+
glm: "GLM",
|
|
37
|
+
minimax: "MINIMAX",
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/** Env vars that exist for each prefix. */
|
|
41
|
+
const ENVVARS = {
|
|
42
|
+
PLANNER: ["PLANNER_MODEL", "PLANNER_BASE_URL", "PLANNER_API_KEY"],
|
|
43
|
+
GLM: ["GLM_MODEL", "GLM_BASE_URL", "GLM_API_KEY"],
|
|
44
|
+
MINIMAX: ["MINIMAX_MODEL", "MINIMAX_BASE_URL", "MINIMAX_API_KEY"],
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
/** Model options derived from a resolved env prefix. */
|
|
48
|
+
export interface ResolvedModel {
|
|
49
|
+
model: string;
|
|
50
|
+
baseURL: string;
|
|
51
|
+
apiKey: string;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Result of routing a request to models. */
|
|
55
|
+
export interface RouteResult {
|
|
56
|
+
planner: ResolvedModel;
|
|
57
|
+
reviewer: ResolvedModel; // master reviewer
|
|
58
|
+
coder: ResolvedModel;
|
|
59
|
+
/** Text of the request with directive comments stripped (safe for agent prompts). */
|
|
60
|
+
cleanRequest: string;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Resolve a model family to actual env-backed model options.
|
|
65
|
+
* family: "GLM" | "PLANNER" | "MINIMAX" (case-insensitive input)
|
|
66
|
+
*/
|
|
67
|
+
export function resolveModel(family: string): ResolvedModel {
|
|
68
|
+
const key = FAMILY_MAP[family.toLowerCase()] ?? family.toUpperCase();
|
|
69
|
+
const env = process.env;
|
|
70
|
+
|
|
71
|
+
const modelKey = `${key}_MODEL`;
|
|
72
|
+
const baseURLKey = `${key}_BASE_URL`;
|
|
73
|
+
const apiKeyKey = `${key}_API_KEY`;
|
|
74
|
+
|
|
75
|
+
const model = env[modelKey];
|
|
76
|
+
const baseURL = env[baseURLKey];
|
|
77
|
+
const apiKey = env[apiKeyKey];
|
|
78
|
+
|
|
79
|
+
if (!apiKey) {
|
|
80
|
+
throw new Error(
|
|
81
|
+
`${apiKeyKey} is not set. Set it in .env (see .env.example).`,
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
if (!model) {
|
|
85
|
+
throw new Error(
|
|
86
|
+
`${modelKey} is not set. Set it in .env (e.g. ${modelKey}="GLM-5.2").`,
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
if (!baseURL) {
|
|
90
|
+
throw new Error(
|
|
91
|
+
`${baseURLKey} is not set. Set it in .env (e.g. ${baseURLKey}="https://api.z.ai/api/v1").`,
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return { model, baseURL, apiKey };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Route a request prompt to the correct model configuration.
|
|
100
|
+
*
|
|
101
|
+
* Algorithm:
|
|
102
|
+
* 1. Extract [role: family] directives from the prompt
|
|
103
|
+
* 2. Resolve each family to actual env-backed model options
|
|
104
|
+
* 3. Apply defaults (planner→PLANNER, reviewer→GLM, coder→MINIMAX)
|
|
105
|
+
* 4. Strip directive comments from the request before passing to agents
|
|
106
|
+
*/
|
|
107
|
+
export function routeRequest(request: string): RouteResult {
|
|
108
|
+
const directives = new Map<string, string>();
|
|
109
|
+
let clean = request;
|
|
110
|
+
|
|
111
|
+
// Extract and strip directives
|
|
112
|
+
let match: RegExpExecArray | null;
|
|
113
|
+
const re = new RegExp(DIRECTIVE_RE.source, "gi");
|
|
114
|
+
while ((match = re.exec(request)) !== null) {
|
|
115
|
+
const [, role, family] = match;
|
|
116
|
+
directives.set(role.toLowerCase(), family.toLowerCase());
|
|
117
|
+
}
|
|
118
|
+
// Remove directive lines/comments from the prompt
|
|
119
|
+
clean = request.replace(DIRECTIVE_RE, "").trim();
|
|
120
|
+
|
|
121
|
+
// Resolve each role
|
|
122
|
+
const plannerFamily = directives.get("planner") ?? "gpt";
|
|
123
|
+
const reviewerFamily = directives.get("reviewer") ?? "glm";
|
|
124
|
+
const coderFamily = directives.get("coder") ?? "minimax";
|
|
125
|
+
|
|
126
|
+
return {
|
|
127
|
+
planner: resolveModel(plannerFamily),
|
|
128
|
+
reviewer: resolveModel(reviewerFamily),
|
|
129
|
+
coder: resolveModel(coderFamily),
|
|
130
|
+
cleanRequest: clean,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** Also match natural-language hints in the request. */
|
|
135
|
+
const NATURAL_HINTS: Array<[RegExp, string, string]> = [
|
|
136
|
+
// Reviewer hints
|
|
137
|
+
[/\bGLM\s+as\s+(master\s+)?reviewer\b/gi, "reviewer", "glm"],
|
|
138
|
+
[/\bGPT\s+as\s+(master\s+)?reviewer\b/gi, "reviewer", "gpt"],
|
|
139
|
+
[/\[reviewer:\s*GLM\]/gi, "reviewer", "glm"],
|
|
140
|
+
[/\[reviewer:\s*GPT\]/gi, "reviewer", "gpt"],
|
|
141
|
+
// Planner hints
|
|
142
|
+
[/\bGPT\s+as\s+planner\b/gi, "planner", "gpt"],
|
|
143
|
+
[/\bGLM\s+as\s+planner\b/gi, "planner", "glm"],
|
|
144
|
+
[/\[planner:\s*GLM\]/gi, "planner", "glm"],
|
|
145
|
+
[/\[planner:\s*GPT\]/gi, "planner", "gpt"],
|
|
146
|
+
// Coder hints
|
|
147
|
+
[/\bMiniMax\s+as\s+coder\b/gi, "coder", "minimax"],
|
|
148
|
+
[/\bGPT\s+as\s+coder\b/gi, "coder", "gpt"],
|
|
149
|
+
[/\[coder:\s*MiniMax\]/gi, "coder", "minimax"],
|
|
150
|
+
[/\[coder:\s*GPT\]/gi, "coder", "gpt"],
|
|
151
|
+
];
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Route a request, also scanning natural-language hints.
|
|
155
|
+
* Directives ([role: family]) take precedence over natural hints.
|
|
156
|
+
*/
|
|
157
|
+
export function routeRequestSmart(request: string): RouteResult {
|
|
158
|
+
const directives = new Map<string, string>();
|
|
159
|
+
const hints = new Map<string, string>();
|
|
160
|
+
let clean = request;
|
|
161
|
+
|
|
162
|
+
// 1. Extract [role: family] directives
|
|
163
|
+
let match: RegExpExecArray | null;
|
|
164
|
+
const re = new RegExp(DIRECTIVE_RE.source, "gi");
|
|
165
|
+
while ((match = re.exec(request)) !== null) {
|
|
166
|
+
const [, role, family] = match;
|
|
167
|
+
directives.set(role.toLowerCase(), family.toLowerCase());
|
|
168
|
+
}
|
|
169
|
+
clean = request.replace(DIRECTIVE_RE, "").trim();
|
|
170
|
+
|
|
171
|
+
// 2. Scan for natural-language hints
|
|
172
|
+
for (const [pattern, role, family] of NATURAL_HINTS) {
|
|
173
|
+
if (!hints.has(role) && pattern.test(request)) {
|
|
174
|
+
hints.set(role, family);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// 3. Merge: directives override hints, hints fill gaps
|
|
179
|
+
const getRole = (role: string, def: string) =>
|
|
180
|
+
directives.get(role) ?? hints.get(role) ?? def;
|
|
181
|
+
|
|
182
|
+
return {
|
|
183
|
+
planner: resolveModel(getRole("planner", "gpt")),
|
|
184
|
+
reviewer: resolveModel(getRole("reviewer", "glm")),
|
|
185
|
+
coder: resolveModel(getRole("coder", "minimax")),
|
|
186
|
+
cleanRequest: clean,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Model factories — all models fully env-driven, no hardcoded defaults.
|
|
3
|
+
*
|
|
4
|
+
* Agent roles (configurable via env):
|
|
5
|
+
* planner → GPT (PLANNER_API_KEY, PLANNER_MODEL, PLANNER_BASE_URL)
|
|
6
|
+
* reviewer → GLM (GLM_API_KEY, GLM_MODEL, GLM_BASE_URL)
|
|
7
|
+
* coder → MiniMax (MINIMAX_API_KEY, MINIMAX_MODEL, MINIMAX_BASE_URL)
|
|
8
|
+
*
|
|
9
|
+
* Wiki: wiki/multi-agent-langchain.md
|
|
10
|
+
*/
|
|
11
|
+
import { ChatOpenAI } from "@langchain/openai";
|
|
12
|
+
export interface ModelOptions {
|
|
13
|
+
/** Override model id (e.g. "gpt-5.6", "GLM-5.2", "MiniMax-M2") */
|
|
14
|
+
model?: string;
|
|
15
|
+
/** Override API base URL */
|
|
16
|
+
baseURL?: string;
|
|
17
|
+
/** Override API key */
|
|
18
|
+
apiKey?: string;
|
|
19
|
+
/** Sampling temperature */
|
|
20
|
+
temperature?: number;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Pre-resolved model config from model-router.ts.
|
|
24
|
+
* Pass this to skip env-lookup and use the routed model directly.
|
|
25
|
+
*/
|
|
26
|
+
export interface ResolvedModelOptions {
|
|
27
|
+
model: string;
|
|
28
|
+
baseURL: string;
|
|
29
|
+
apiKey: string;
|
|
30
|
+
/** Sampling temperature */
|
|
31
|
+
temperature?: number;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Planner model — GPT family.
|
|
35
|
+
*
|
|
36
|
+
* Can be called with:
|
|
37
|
+
* - ModelOptions (override fields, rest from env)
|
|
38
|
+
* - ResolvedModelOptions (fully resolved from model-router, no env lookup)
|
|
39
|
+
*
|
|
40
|
+
* Env vars (when using ModelOptions):
|
|
41
|
+
* PLANNER_API_KEY (required)
|
|
42
|
+
* PLANNER_MODEL (required)
|
|
43
|
+
* PLANNER_BASE_URL (required)
|
|
44
|
+
*/
|
|
45
|
+
export declare function createPlannerModel(opts?: ModelOptions | ResolvedModelOptions): ChatOpenAI;
|
|
46
|
+
/**
|
|
47
|
+
* Reviewer model — GLM family (master reviewer per design).
|
|
48
|
+
*
|
|
49
|
+
* Can be called with:
|
|
50
|
+
* - ModelOptions (override fields, rest from env)
|
|
51
|
+
* - ResolvedModelOptions (fully resolved from model-router, no env lookup)
|
|
52
|
+
*
|
|
53
|
+
* Env vars (when using ModelOptions):
|
|
54
|
+
* GLM_API_KEY (required)
|
|
55
|
+
* GLM_MODEL (required)
|
|
56
|
+
* GLM_BASE_URL (required)
|
|
57
|
+
*/
|
|
58
|
+
export declare function createReviewerModel(opts?: ModelOptions | ResolvedModelOptions): ChatOpenAI;
|
|
59
|
+
/**
|
|
60
|
+
* Coder model — MiniMax family.
|
|
61
|
+
*
|
|
62
|
+
* Can be called with:
|
|
63
|
+
* - ModelOptions (override fields, rest from env)
|
|
64
|
+
* - ResolvedModelOptions (fully resolved from model-router, no env lookup)
|
|
65
|
+
*
|
|
66
|
+
* Env vars (when using ModelOptions):
|
|
67
|
+
* MINIMAX_API_KEY (required)
|
|
68
|
+
* MINIMAX_MODEL (required)
|
|
69
|
+
* MINIMAX_BASE_URL (required)
|
|
70
|
+
*/
|
|
71
|
+
export declare function createCoderModel(opts?: ModelOptions | ResolvedModelOptions): ChatOpenAI;
|
|
72
|
+
//# sourceMappingURL=models.d.ts.map
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Model factories —
|
|
2
|
+
* Model factories — all models fully env-driven, no hardcoded defaults.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Agent roles (configurable via env):
|
|
5
|
+
* planner → GPT (PLANNER_API_KEY, PLANNER_MODEL, PLANNER_BASE_URL)
|
|
6
|
+
* reviewer → GLM (GLM_API_KEY, GLM_MODEL, GLM_BASE_URL)
|
|
7
|
+
* coder → MiniMax (MINIMAX_API_KEY, MINIMAX_MODEL, MINIMAX_BASE_URL)
|
|
6
8
|
*
|
|
7
9
|
* Wiki: wiki/multi-agent-langchain.md
|
|
8
10
|
*/
|
|
@@ -10,7 +12,7 @@
|
|
|
10
12
|
import { ChatOpenAI } from "@langchain/openai";
|
|
11
13
|
|
|
12
14
|
export interface ModelOptions {
|
|
13
|
-
/** Override model id (e.g. "gpt-
|
|
15
|
+
/** Override model id (e.g. "gpt-5.6", "GLM-5.2", "MiniMax-M2") */
|
|
14
16
|
model?: string;
|
|
15
17
|
/** Override API base URL */
|
|
16
18
|
baseURL?: string;
|
|
@@ -20,76 +22,136 @@ export interface ModelOptions {
|
|
|
20
22
|
temperature?: number;
|
|
21
23
|
}
|
|
22
24
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
25
|
+
/**
|
|
26
|
+
* Pre-resolved model config from model-router.ts.
|
|
27
|
+
* Pass this to skip env-lookup and use the routed model directly.
|
|
28
|
+
*/
|
|
29
|
+
export interface ResolvedModelOptions {
|
|
30
|
+
model: string;
|
|
31
|
+
baseURL: string;
|
|
32
|
+
apiKey: string;
|
|
33
|
+
/** Sampling temperature */
|
|
34
|
+
temperature?: number;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// ─── Shared helpers ──────────────────────────────────────────────────────────
|
|
38
|
+
|
|
39
|
+
/** Build the `configuration.baseURL` override for ChatOpenAI. */
|
|
40
|
+
function baseURLConfig(baseURL: string): {
|
|
41
|
+
configuration: { baseURL: string };
|
|
42
|
+
} {
|
|
43
|
+
return { configuration: { baseURL } };
|
|
36
44
|
}
|
|
37
45
|
|
|
38
|
-
|
|
46
|
+
/** Env-var tuple for a single model family. */
|
|
47
|
+
interface ModelEnv {
|
|
39
48
|
model: string;
|
|
40
49
|
baseURL: string;
|
|
41
50
|
apiKey: string;
|
|
42
|
-
}
|
|
43
|
-
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Read env vars for a model family. Throws if the API key is absent. */
|
|
54
|
+
function readEnv(prefix: string, env: typeof process.env): ModelEnv {
|
|
55
|
+
const apiKey = env[`${prefix}_API_KEY`] ?? "";
|
|
44
56
|
if (!apiKey) {
|
|
45
57
|
throw new Error(
|
|
46
|
-
|
|
58
|
+
`${prefix}_API_KEY is not set. Add it to .env (see .env.example).\n` +
|
|
59
|
+
` Example: ${prefix}_MODEL="GLM-5.2" ${prefix}_BASE_URL="https://api.z.ai/api/v1"`,
|
|
47
60
|
);
|
|
48
61
|
}
|
|
49
62
|
return {
|
|
50
|
-
model: process.env.MINIMAX_MODEL ?? "MiniMax-M2",
|
|
51
|
-
// International endpoint by default; China mainland: https://api.minimax.chat/v1
|
|
52
|
-
baseURL: process.env.MINIMAX_BASE_URL ?? "https://api.minimaxi.com/v1",
|
|
53
63
|
apiKey,
|
|
64
|
+
model:
|
|
65
|
+
env[`${prefix}_MODEL`] ??
|
|
66
|
+
(() => {
|
|
67
|
+
throw new Error(
|
|
68
|
+
`${prefix}_MODEL is not set. Set it in .env (e.g. ${prefix}_MODEL="GLM-5.2").`,
|
|
69
|
+
);
|
|
70
|
+
})(),
|
|
71
|
+
baseURL:
|
|
72
|
+
env[`${prefix}_BASE_URL`] ??
|
|
73
|
+
(() => {
|
|
74
|
+
throw new Error(
|
|
75
|
+
`${prefix}_BASE_URL is not set. Set it in .env (e.g. ${prefix}_BASE_URL="https://api.openai.com/v1").`,
|
|
76
|
+
);
|
|
77
|
+
})(),
|
|
54
78
|
};
|
|
55
79
|
}
|
|
56
80
|
|
|
57
|
-
|
|
58
|
-
function baseURLConfig(baseURL: string): {
|
|
59
|
-
configuration: { baseURL: string };
|
|
60
|
-
} {
|
|
61
|
-
return { configuration: { baseURL } };
|
|
62
|
-
}
|
|
81
|
+
// ─── Model factories ─────────────────────────────────────────────────────────
|
|
63
82
|
|
|
64
|
-
/**
|
|
65
|
-
|
|
66
|
-
|
|
83
|
+
/**
|
|
84
|
+
* Planner model — GPT family.
|
|
85
|
+
*
|
|
86
|
+
* Can be called with:
|
|
87
|
+
* - ModelOptions (override fields, rest from env)
|
|
88
|
+
* - ResolvedModelOptions (fully resolved from model-router, no env lookup)
|
|
89
|
+
*
|
|
90
|
+
* Env vars (when using ModelOptions):
|
|
91
|
+
* PLANNER_API_KEY (required)
|
|
92
|
+
* PLANNER_MODEL (required)
|
|
93
|
+
* PLANNER_BASE_URL (required)
|
|
94
|
+
*/
|
|
95
|
+
export function createPlannerModel(
|
|
96
|
+
opts: ModelOptions | ResolvedModelOptions = {},
|
|
97
|
+
): ChatOpenAI {
|
|
98
|
+
const resolved = "baseURL" in opts ? opts : readEnv("PLANNER", process.env);
|
|
99
|
+
// resolved.baseURL is always a string (readEnv throws if absent); cast is safe
|
|
100
|
+
const baseURL = (opts.baseURL ?? resolved.baseURL) as string;
|
|
67
101
|
return new ChatOpenAI({
|
|
68
|
-
model: opts.model ??
|
|
69
|
-
apiKey: opts.apiKey ??
|
|
102
|
+
model: opts.model ?? resolved.model,
|
|
103
|
+
apiKey: opts.apiKey ?? resolved.apiKey,
|
|
70
104
|
temperature: opts.temperature ?? 0.2,
|
|
71
|
-
...baseURLConfig(
|
|
105
|
+
...baseURLConfig(baseURL),
|
|
72
106
|
});
|
|
73
107
|
}
|
|
74
108
|
|
|
75
|
-
/**
|
|
76
|
-
|
|
77
|
-
|
|
109
|
+
/**
|
|
110
|
+
* Reviewer model — GLM family (master reviewer per design).
|
|
111
|
+
*
|
|
112
|
+
* Can be called with:
|
|
113
|
+
* - ModelOptions (override fields, rest from env)
|
|
114
|
+
* - ResolvedModelOptions (fully resolved from model-router, no env lookup)
|
|
115
|
+
*
|
|
116
|
+
* Env vars (when using ModelOptions):
|
|
117
|
+
* GLM_API_KEY (required)
|
|
118
|
+
* GLM_MODEL (required)
|
|
119
|
+
* GLM_BASE_URL (required)
|
|
120
|
+
*/
|
|
121
|
+
export function createReviewerModel(
|
|
122
|
+
opts: ModelOptions | ResolvedModelOptions = {},
|
|
123
|
+
): ChatOpenAI {
|
|
124
|
+
const resolved = "baseURL" in opts ? opts : readEnv("GLM", process.env);
|
|
125
|
+
const baseURL = (opts.baseURL ?? resolved.baseURL) as string;
|
|
78
126
|
return new ChatOpenAI({
|
|
79
|
-
model: opts.model ??
|
|
80
|
-
apiKey: opts.apiKey ??
|
|
81
|
-
temperature: opts.temperature ?? 0.1,
|
|
82
|
-
...baseURLConfig(
|
|
127
|
+
model: opts.model ?? resolved.model,
|
|
128
|
+
apiKey: opts.apiKey ?? resolved.apiKey,
|
|
129
|
+
temperature: opts.temperature ?? 0.1,
|
|
130
|
+
...baseURLConfig(baseURL),
|
|
83
131
|
});
|
|
84
132
|
}
|
|
85
133
|
|
|
86
|
-
/**
|
|
87
|
-
|
|
88
|
-
|
|
134
|
+
/**
|
|
135
|
+
* Coder model — MiniMax family.
|
|
136
|
+
*
|
|
137
|
+
* Can be called with:
|
|
138
|
+
* - ModelOptions (override fields, rest from env)
|
|
139
|
+
* - ResolvedModelOptions (fully resolved from model-router, no env lookup)
|
|
140
|
+
*
|
|
141
|
+
* Env vars (when using ModelOptions):
|
|
142
|
+
* MINIMAX_API_KEY (required)
|
|
143
|
+
* MINIMAX_MODEL (required)
|
|
144
|
+
* MINIMAX_BASE_URL (required)
|
|
145
|
+
*/
|
|
146
|
+
export function createCoderModel(
|
|
147
|
+
opts: ModelOptions | ResolvedModelOptions = {},
|
|
148
|
+
): ChatOpenAI {
|
|
149
|
+
const resolved = "baseURL" in opts ? opts : readEnv("MINIMAX", process.env);
|
|
150
|
+
const baseURL = (opts.baseURL ?? resolved.baseURL) as string;
|
|
89
151
|
return new ChatOpenAI({
|
|
90
|
-
model: opts.model ??
|
|
91
|
-
apiKey: opts.apiKey ??
|
|
152
|
+
model: opts.model ?? resolved.model,
|
|
153
|
+
apiKey: opts.apiKey ?? resolved.apiKey,
|
|
92
154
|
temperature: opts.temperature ?? 0.3,
|
|
93
|
-
...baseURLConfig(
|
|
155
|
+
...baseURLConfig(baseURL),
|
|
94
156
|
});
|
|
95
157
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { PingPongDecisionEngine, type PingPongDecisionOptions, type PingPongDecisionResult } from "../../packages/intent-analyzer/src/index.js";
|
|
2
|
+
export interface PingPongMiddlewareOptions extends PingPongDecisionOptions {
|
|
3
|
+
/** Shared directory watched by LoopDaemon. Defaults to /tmp/herdr-workspace. */
|
|
4
|
+
workspace?: string;
|
|
5
|
+
/** Repository root and blackboard owner. Defaults to process.cwd(). */
|
|
6
|
+
projectRoot?: string;
|
|
7
|
+
/** Agent or terminal identifier for audit records. */
|
|
8
|
+
actorId?: string;
|
|
9
|
+
/** Also enqueue suggest_ping_pong decisions. Default: false. */
|
|
10
|
+
enqueueSuggestions?: boolean;
|
|
11
|
+
/** Test hook for stable timestamps. */
|
|
12
|
+
now?: () => Date;
|
|
13
|
+
}
|
|
14
|
+
export interface PingPongMiddlewareResult {
|
|
15
|
+
decision: PingPongDecisionResult;
|
|
16
|
+
enqueued: boolean;
|
|
17
|
+
taskId?: string;
|
|
18
|
+
taskFile?: string;
|
|
19
|
+
auditFile: string;
|
|
20
|
+
blackboardFile: string;
|
|
21
|
+
}
|
|
22
|
+
export declare class PingPongMiddleware {
|
|
23
|
+
private readonly engine;
|
|
24
|
+
constructor(engine?: PingPongDecisionEngine);
|
|
25
|
+
submit(request: string, options?: PingPongMiddlewareOptions): PingPongMiddlewareResult;
|
|
26
|
+
}
|
|
27
|
+
export declare function submitPingPongIntent(request: string, options?: PingPongMiddlewareOptions): PingPongMiddlewareResult;
|
|
28
|
+
//# sourceMappingURL=ping-pong-middleware.d.ts.map
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { describe, it } from "node:test";
|
|
2
|
+
import { equal, ok } from "node:assert";
|
|
3
|
+
import { mkdtempSync, readFileSync, rmSync } from "node:fs";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { tmpdir } from "node:os";
|
|
6
|
+
import { PingPongMiddleware } from "./ping-pong-middleware.js";
|
|
7
|
+
|
|
8
|
+
describe("PingPongMiddleware", () => {
|
|
9
|
+
it("records explicit PING-PONG decisions in blackboard and inbox", () => {
|
|
10
|
+
const projectRoot = mkdtempSync(join(tmpdir(), "ping-pong-project-"));
|
|
11
|
+
const workspace = mkdtempSync(join(tmpdir(), "ping-pong-workspace-"));
|
|
12
|
+
try {
|
|
13
|
+
const result = new PingPongMiddleware().submit(
|
|
14
|
+
"Good plan, then write down plan to wiki/ then I will ask Minimax to write code then you review",
|
|
15
|
+
{
|
|
16
|
+
projectRoot,
|
|
17
|
+
workspace,
|
|
18
|
+
actorId: "test-agent",
|
|
19
|
+
now: () => new Date("2026-09-10T00:00:00.000Z"),
|
|
20
|
+
},
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
equal(result.enqueued, true);
|
|
24
|
+
ok(result.taskFile);
|
|
25
|
+
ok(result.taskId?.endsWith(".md"));
|
|
26
|
+
const taskBody = readFileSync(result.taskFile!, "utf8");
|
|
27
|
+
ok(taskBody.includes("kind: ping-pong"));
|
|
28
|
+
ok(taskBody.includes("ask Minimax to write code"));
|
|
29
|
+
|
|
30
|
+
const blackboard = JSON.parse(
|
|
31
|
+
readFileSync(join(projectRoot, ".write-review", "status.json"), "utf8"),
|
|
32
|
+
);
|
|
33
|
+
equal(blackboard.mode, "ping_pong");
|
|
34
|
+
equal(blackboard.phase, "writing");
|
|
35
|
+
equal(blackboard.pingPong.decision, "run_ping_pong");
|
|
36
|
+
equal(blackboard.pingPong.taskId, result.taskId);
|
|
37
|
+
|
|
38
|
+
const audit = readFileSync(result.auditFile, "utf8");
|
|
39
|
+
ok(audit.includes("run_ping_pong"));
|
|
40
|
+
ok(audit.includes("test-agent"));
|
|
41
|
+
} finally {
|
|
42
|
+
rmSync(projectRoot, { recursive: true, force: true });
|
|
43
|
+
rmSync(workspace, { recursive: true, force: true });
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("records inline decisions without enqueueing", () => {
|
|
48
|
+
const projectRoot = mkdtempSync(join(tmpdir(), "ping-pong-project-"));
|
|
49
|
+
const workspace = mkdtempSync(join(tmpdir(), "ping-pong-workspace-"));
|
|
50
|
+
try {
|
|
51
|
+
const result = new PingPongMiddleware().submit(
|
|
52
|
+
"analyze why the loop is quiet, do not edit code",
|
|
53
|
+
{ projectRoot, workspace, actorId: "test-agent" },
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
equal(result.enqueued, false);
|
|
57
|
+
equal(result.taskFile, undefined);
|
|
58
|
+
const blackboard = JSON.parse(
|
|
59
|
+
readFileSync(join(projectRoot, ".write-review", "status.json"), "utf8"),
|
|
60
|
+
);
|
|
61
|
+
equal(blackboard.mode, "ping_pong");
|
|
62
|
+
equal(blackboard.phase, "idle");
|
|
63
|
+
equal(blackboard.pingPong.decision, "handle_inline");
|
|
64
|
+
} finally {
|
|
65
|
+
rmSync(projectRoot, { recursive: true, force: true });
|
|
66
|
+
rmSync(workspace, { recursive: true, force: true });
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
});
|