pi-harness-runtime 1.1.44 → 1.1.45
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/harness/langchain/agents.ts +15 -3
- package/harness/langchain/daemon.ts +126 -1
- package/harness/langchain/file-tools.ts +178 -0
- package/harness/langchain/graph.ts +10 -5
- package/harness/langchain/status-line.test.ts +92 -0
- package/harness/langchain/status-line.ts +255 -0
- package/package.json +1 -1
- 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/package.json +1 -1
- 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/package.json +1 -1
- package/packages/token-estimation/package.json +1 -1
- package/packages/token-optimizer/package.json +1 -1
- 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
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Status line manager — drives the harness loop status in the pi host.
|
|
3
|
+
*
|
|
4
|
+
* Architecture:
|
|
5
|
+
*
|
|
6
|
+
* Pi-lens installed?
|
|
7
|
+
* ├─ YES → LoopWidget.makeRenderer() → pi host's ui.setWidget("harness-loop", ...)
|
|
8
|
+
* └─ NO → write to .harness-status → pi host reads and displays
|
|
9
|
+
*
|
|
10
|
+
* The status line shows two tiers of information:
|
|
11
|
+
* [loop status] [API quota]
|
|
12
|
+
* 🧭 planning MiniMax: 5h: 89% left · week: 90% left
|
|
13
|
+
*
|
|
14
|
+
* API quota is read from ~/.pi-harness/quota.json (written by the pi host
|
|
15
|
+
* process that has access to the TUI quota signals). The daemon polls this
|
|
16
|
+
* file every 60s so the status line stays fresh even when no task is running.
|
|
17
|
+
*
|
|
18
|
+
* Wiki: wiki/pi-lens-status-line.md
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
|
|
22
|
+
import { dirname, join } from "node:path";
|
|
23
|
+
import { homedir } from "node:os";
|
|
24
|
+
|
|
25
|
+
// ─── Quota types ─────────────────────────────────────────────────────────────
|
|
26
|
+
|
|
27
|
+
export interface QuotaData {
|
|
28
|
+
/** MiniMax 5-hour window usage percentage (0–100). Undefined = unknown. */
|
|
29
|
+
fiveHourPercent?: number;
|
|
30
|
+
/** MiniMax weekly window usage percentage (0–100). Undefined = unknown. */
|
|
31
|
+
weeklyPercent?: number;
|
|
32
|
+
/** ISO timestamp of when the 5-hour quota resets. */
|
|
33
|
+
fiveHourResetAt?: string;
|
|
34
|
+
/** ISO timestamp of when the weekly quota resets. */
|
|
35
|
+
weeklyResetAt?: string;
|
|
36
|
+
/** Raw message from GLM quota signal (for display) */
|
|
37
|
+
rawMessage?: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
interface QuotaFile {
|
|
41
|
+
miniMax?: {
|
|
42
|
+
fiveHour?: { percent?: number; resetAt?: string };
|
|
43
|
+
weekly?: { percent?: number; resetAt?: string };
|
|
44
|
+
};
|
|
45
|
+
updatedAt?: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// ─── Detection ───────────────────────────────────────────────────────────────
|
|
49
|
+
|
|
50
|
+
/** True when the daemon is running inside a pi-lens-equipped pi host. */
|
|
51
|
+
export function isPiLensAvailable(): boolean {
|
|
52
|
+
return (
|
|
53
|
+
Boolean(process.env.PI_LENS_PID) ||
|
|
54
|
+
Boolean(process.env.PI_LENS_SOCKET) ||
|
|
55
|
+
process.env.PI_LENS === "1"
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// ─── Status line format ──────────────────────────────────────────────────────
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Format a quota percentage for display.
|
|
63
|
+
* Shows "N% left" when known, "?" when unknown.
|
|
64
|
+
*/
|
|
65
|
+
function fmtPercent(p?: number): string {
|
|
66
|
+
if (p === undefined || p < 0) return "?";
|
|
67
|
+
if (p >= 100) return "0% left";
|
|
68
|
+
return `${p}% left`;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Format an ISO reset timestamp as a human-readable countdown.
|
|
73
|
+
* "in 2h 30m", "in 45m", "at 19:17", etc.
|
|
74
|
+
*/
|
|
75
|
+
function fmtResetAt(iso?: string): string {
|
|
76
|
+
if (!iso) return "";
|
|
77
|
+
try {
|
|
78
|
+
const reset = new Date(iso);
|
|
79
|
+
if (isNaN(reset.getTime())) return "";
|
|
80
|
+
const now = new Date();
|
|
81
|
+
const diffMs = reset.getTime() - now.getTime();
|
|
82
|
+
if (diffMs <= 0) return " (resetting soon)";
|
|
83
|
+
const mins = Math.floor(diffMs / 60_000);
|
|
84
|
+
const hours = Math.floor(mins / 60);
|
|
85
|
+
const remMins = mins % 60;
|
|
86
|
+
if (hours > 0) return ` (resets in ${hours}h ${remMins}m)`;
|
|
87
|
+
return ` (resets in ${remMins}m)`;
|
|
88
|
+
} catch {
|
|
89
|
+
return "";
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Build a one-line status string.
|
|
95
|
+
* Format: "MiniMax: 5h: 89% left (resets in 2h) · week: 90% left"
|
|
96
|
+
*/
|
|
97
|
+
export function formatQuotaLine(q: QuotaData): string {
|
|
98
|
+
const fiveHour = q.fiveHourPercent === undefined
|
|
99
|
+
? null
|
|
100
|
+
: `5h: ${fmtPercent(q.fiveHourPercent)}${fmtResetAt(q.fiveHourResetAt)}`;
|
|
101
|
+
const weekly = q.weeklyPercent === undefined
|
|
102
|
+
? null
|
|
103
|
+
: `week: ${fmtPercent(q.weeklyPercent)}`;
|
|
104
|
+
|
|
105
|
+
const parts = [fiveHour, weekly].filter(Boolean);
|
|
106
|
+
if (parts.length === 0) return "MiniMax: quota unknown";
|
|
107
|
+
return `MiniMax: ${parts.join(" · ")}`;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// ─── StatusLineManager ───────────────────────────────────────────────────────
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Drives the harness status line in the pi host.
|
|
114
|
+
*
|
|
115
|
+
* - When pi-lens is available: provides the LoopWidget renderer factory
|
|
116
|
+
* for the pi host to integrate via `ui.setWidget("harness-loop", factory)`.
|
|
117
|
+
* - When pi-lens is not available: writes a plain-text status to
|
|
118
|
+
* `<workspace>/.harness-status` every time update() is called.
|
|
119
|
+
*
|
|
120
|
+
* The pi host can read `.harness-status` and display it in its own UI.
|
|
121
|
+
*/
|
|
122
|
+
export class StatusLineManager {
|
|
123
|
+
private readonly workspace: string;
|
|
124
|
+
private readonly quotaFile: string;
|
|
125
|
+
private lastQuota: QuotaData = {};
|
|
126
|
+
private timer?: ReturnType<typeof setInterval>;
|
|
127
|
+
private loopStatus = "";
|
|
128
|
+
|
|
129
|
+
constructor(workspace: string) {
|
|
130
|
+
this.workspace = workspace;
|
|
131
|
+
// Quota file written by the pi host (which has access to TUI signals)
|
|
132
|
+
this.quotaFile = join(homedir(), ".pi-harness", "quota.json");
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// ─── Public API ──────────────────────────────────────────────────────────
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Start polling the quota file.
|
|
139
|
+
* Call once after construction.
|
|
140
|
+
*/
|
|
141
|
+
start(): void {
|
|
142
|
+
this._pollQuota();
|
|
143
|
+
this.timer = setInterval(() => this._pollQuota(), 60_000);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** Stop polling. */
|
|
147
|
+
stop(): void {
|
|
148
|
+
if (this.timer) {
|
|
149
|
+
clearInterval(this.timer);
|
|
150
|
+
this.timer = undefined;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Update the loop status (planning/writing/reviewing/finished).
|
|
156
|
+
* Call this from the daemon's onStep handler.
|
|
157
|
+
*/
|
|
158
|
+
updateLoopStatus(status: string): void {
|
|
159
|
+
this.loopStatus = status;
|
|
160
|
+
this._writeStatusFile();
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Update the full status — call after each loop transition.
|
|
165
|
+
* Writes the status file (when no pi-lens) and logs to console.
|
|
166
|
+
*/
|
|
167
|
+
update(data: { loopStatus?: string; quota?: QuotaData }): void {
|
|
168
|
+
if (data.loopStatus !== undefined) this.loopStatus = data.loopStatus;
|
|
169
|
+
if (data.quota) this.lastQuota = data.quota;
|
|
170
|
+
this._writeStatusFile();
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Returns a rendered one-line status string for the pi host to display.
|
|
175
|
+
* Combines loop status + quota into a single readable line.
|
|
176
|
+
*/
|
|
177
|
+
getLine(): string {
|
|
178
|
+
const quotaLine = formatQuotaLine(this.lastQuota);
|
|
179
|
+
if (this.loopStatus) {
|
|
180
|
+
return `${this.loopStatus} | ${quotaLine}`;
|
|
181
|
+
}
|
|
182
|
+
return quotaLine;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// ─── Internal ───────────────────────────────────────────────────────────
|
|
186
|
+
|
|
187
|
+
private _pollQuota(): void {
|
|
188
|
+
try {
|
|
189
|
+
if (!existsSync(this.quotaFile)) return;
|
|
190
|
+
const raw = readFileSync(this.quotaFile, "utf8");
|
|
191
|
+
const data = JSON.parse(raw) as QuotaFile;
|
|
192
|
+
if (data.miniMax) {
|
|
193
|
+
this.lastQuota = {
|
|
194
|
+
fiveHourPercent: data.miniMax.fiveHour?.percent,
|
|
195
|
+
fiveHourResetAt: data.miniMax.fiveHour?.resetAt,
|
|
196
|
+
weeklyPercent: data.miniMax.weekly?.percent,
|
|
197
|
+
weeklyResetAt: data.miniMax.weekly?.resetAt,
|
|
198
|
+
rawMessage: data.miniMax.fiveHour?.resetAt
|
|
199
|
+
? `5h quota resets at ${data.miniMax.fiveHour.resetAt}`
|
|
200
|
+
: undefined,
|
|
201
|
+
};
|
|
202
|
+
this._writeStatusFile();
|
|
203
|
+
}
|
|
204
|
+
} catch {
|
|
205
|
+
// Quota file missing or invalid — skip
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
private _writeStatusFile(): void {
|
|
210
|
+
if (isPiLensAvailable()) return; // pi-lens handles its own display
|
|
211
|
+
|
|
212
|
+
const line = this.getLine();
|
|
213
|
+
const statusFile = join(this.workspace, ".harness-status");
|
|
214
|
+
try {
|
|
215
|
+
// Ensure directory exists
|
|
216
|
+
const dir = dirname(statusFile);
|
|
217
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
218
|
+
writeFileSync(statusFile, line + "\n", "utf8");
|
|
219
|
+
} catch {
|
|
220
|
+
// Ignore write errors (read-only filesystem, etc.)
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// ─── Quota file writer (for the pi host) ─────────────────────────────────────
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Write quota data to the shared quota file.
|
|
229
|
+
* Call this from the pi host process that receives GLM TUI signals.
|
|
230
|
+
*
|
|
231
|
+
* The daemon reads this file to keep its status line fresh.
|
|
232
|
+
*/
|
|
233
|
+
export function writeQuotaFile(data: QuotaData): void {
|
|
234
|
+
const dir = join(homedir(), ".pi-harness");
|
|
235
|
+
const file = join(dir, "quota.json");
|
|
236
|
+
try {
|
|
237
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
238
|
+
const payload: QuotaFile = {
|
|
239
|
+
miniMax: {
|
|
240
|
+
fiveHour: {
|
|
241
|
+
percent: data.fiveHourPercent,
|
|
242
|
+
resetAt: data.fiveHourResetAt,
|
|
243
|
+
},
|
|
244
|
+
weekly: {
|
|
245
|
+
percent: data.weeklyPercent,
|
|
246
|
+
resetAt: data.weeklyResetAt,
|
|
247
|
+
},
|
|
248
|
+
},
|
|
249
|
+
updatedAt: new Date().toISOString(),
|
|
250
|
+
};
|
|
251
|
+
writeFileSync(file, JSON.stringify(payload, null, 2), "utf8");
|
|
252
|
+
} catch {
|
|
253
|
+
// Ignore write errors
|
|
254
|
+
}
|
|
255
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-harness/capability-registry",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.45",
|
|
4
4
|
"description": "Capability Registry for pi-harness-runtime — tracks model capabilities and enables capability-based routing",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|