pi-harness-runtime 1.1.255 → 1.1.257
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/index.ts +10 -2
- package/package.json +1 -1
- package/packages/a2a-adapter/package.json +1 -1
- package/packages/agent-policy/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/codex-watcher/package.json +1 -1
- package/packages/codex-watcher/test/watch.test.ts +303 -0
- 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/e2e-tester/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/jev-judge/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/dist/adapters/telegram-adapter.d.ts +11 -0
- package/packages/notification/dist/adapters/telegram-adapter.d.ts.map +1 -1
- package/packages/notification/dist/adapters/telegram-adapter.js +89 -5
- package/packages/notification/dist/adapters/telegram-adapter.js.map +1 -1
- package/packages/notification/package.json +1 -1
- package/packages/notification/src/adapters/telegram-adapter.ts +81 -5
- package/packages/observability/package.json +1 -1
- package/packages/okf-indexer/package.json +1 -1
- package/packages/paperclip-integration/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/qdrant-skills/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/skills/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/telegram/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/trajectory/package.json +1 -1
- package/packages/tui/package.json +1 -1
- package/packages/types/package.json +1 -1
- package/packages/webmcp-tester/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/index.ts
CHANGED
|
@@ -946,7 +946,12 @@ async function initTelegram(pi: ExtensionAPI): Promise<void> {
|
|
|
946
946
|
|
|
947
947
|
// Log the actual bot username (fetched from Telegram API via getMe)
|
|
948
948
|
const botUsername = center.getTelegramBotUsername();
|
|
949
|
-
const
|
|
949
|
+
const telegram = center.getTelegramAdapter();
|
|
950
|
+
telegram?.setGreetingVerifiedHandler(() => {
|
|
951
|
+
addHarnessMessage("Telegram", "ready (Hi reply received; two-way verified)", "ok");
|
|
952
|
+
});
|
|
953
|
+
pi.on("session_shutdown", () => telegram?.stopPolling());
|
|
954
|
+
const greeting = await telegram?.sendStartupGreeting();
|
|
950
955
|
if (!greeting?.success) {
|
|
951
956
|
logStartup("[pi-harness] Telegram: startup greeting delivery failed");
|
|
952
957
|
addHarnessMessage("Telegram", "bot verified; Hi delivery failed", "err", [
|
|
@@ -955,7 +960,10 @@ async function initTelegram(pi: ExtensionAPI): Promise<void> {
|
|
|
955
960
|
]);
|
|
956
961
|
return;
|
|
957
962
|
}
|
|
958
|
-
|
|
963
|
+
telegram?.startPolling();
|
|
964
|
+
if (!telegram?.isGreetingVerified()) {
|
|
965
|
+
addHarnessMessage("Telegram", "Hi sent; tap Say Hi back to verify inbound", "wait");
|
|
966
|
+
}
|
|
959
967
|
if (botUsername) {
|
|
960
968
|
logStartup(`[pi-harness] Telegram (@${botUsername}) ready — DM the bot to receive alerts`);
|
|
961
969
|
} else {
|
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.257",
|
|
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",
|
|
@@ -148,3 +148,306 @@ describe("classifyMessage", () => {
|
|
|
148
148
|
expect(classifyMessage(" ")).toBe("unknown");
|
|
149
149
|
});
|
|
150
150
|
});
|
|
151
|
+
|
|
152
|
+
// -----------------------------------------------------------------------
|
|
153
|
+
// UTF-8 Byte Buffering (simulated)
|
|
154
|
+
// -----------------------------------------------------------------------
|
|
155
|
+
|
|
156
|
+
describe("UTF-8 byte buffering", () => {
|
|
157
|
+
/**
|
|
158
|
+
* Simulates the byte buffering logic from index.ts
|
|
159
|
+
* This tests the core algorithm without needing actual file I/O
|
|
160
|
+
*/
|
|
161
|
+
function simulateByteBuffering(chunks: Buffer[], newlineTerminated: boolean[]): {
|
|
162
|
+
completeLines: string[];
|
|
163
|
+
remainingBytes: Buffer | null;
|
|
164
|
+
} {
|
|
165
|
+
let pendingBytes: Buffer | null = null;
|
|
166
|
+
|
|
167
|
+
for (let i = 0; i < chunks.length; i++) {
|
|
168
|
+
const chunk = chunks[i];
|
|
169
|
+
|
|
170
|
+
// Buffer raw bytes
|
|
171
|
+
if (pendingBytes) {
|
|
172
|
+
pendingBytes = Buffer.concat([pendingBytes, chunk]);
|
|
173
|
+
} else {
|
|
174
|
+
pendingBytes = chunk;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// If this chunk is newline-terminated, we can decode
|
|
178
|
+
if (newlineTerminated[i]) {
|
|
179
|
+
const text = pendingBytes.toString("utf8");
|
|
180
|
+
pendingBytes = null;
|
|
181
|
+
return {
|
|
182
|
+
completeLines: text.split("\n").filter(l => l.trim()),
|
|
183
|
+
remainingBytes: pendingBytes,
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return {
|
|
189
|
+
completeLines: [],
|
|
190
|
+
remainingBytes: pendingBytes,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Stateful simulation that properly chains multiple reads
|
|
196
|
+
*/
|
|
197
|
+
function createBufferingState() {
|
|
198
|
+
let pendingBytes: Buffer | null = null;
|
|
199
|
+
let linesEmitted: string[] = [];
|
|
200
|
+
|
|
201
|
+
function processChunk(chunk: Buffer, endsWithNewline: boolean): string[] {
|
|
202
|
+
// Buffer raw bytes
|
|
203
|
+
if (pendingBytes) {
|
|
204
|
+
pendingBytes = Buffer.concat([pendingBytes, chunk]);
|
|
205
|
+
} else {
|
|
206
|
+
pendingBytes = chunk;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (endsWithNewline) {
|
|
210
|
+
const text = pendingBytes.toString("utf8");
|
|
211
|
+
const lines = text.split("\n").filter(l => l.trim());
|
|
212
|
+
linesEmitted.push(...lines);
|
|
213
|
+
pendingBytes = null;
|
|
214
|
+
return lines;
|
|
215
|
+
}
|
|
216
|
+
return [];
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return { processChunk, getPending: () => pendingBytes, getEmitted: () => linesEmitted };
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
it("preserves split multi-byte UTF-8 characters", () => {
|
|
223
|
+
// 中文 is 3 bytes per character: \u4E2D\u6587
|
|
224
|
+
// If we split between bytes, each chunk should still decode correctly
|
|
225
|
+
const fullText = '{"text": "Hello 中文 world"}\n';
|
|
226
|
+
const fullBuffer = Buffer.from(fullText, "utf8");
|
|
227
|
+
|
|
228
|
+
// Split in the middle of 中文 (after 2nd byte of first char)
|
|
229
|
+
const chineseStart = fullText.indexOf("中文");
|
|
230
|
+
const splitPoint = chineseStart + 2; // Split in middle of first Chinese char
|
|
231
|
+
|
|
232
|
+
const chunk1 = fullBuffer.subarray(0, splitPoint);
|
|
233
|
+
const chunk2 = fullBuffer.subarray(splitPoint);
|
|
234
|
+
|
|
235
|
+
// Use stateful buffering to properly chain reads
|
|
236
|
+
const state = createBufferingState();
|
|
237
|
+
|
|
238
|
+
// Process chunk 1 (not newline terminated)
|
|
239
|
+
const lines1 = state.processChunk(chunk1, false);
|
|
240
|
+
expect(lines1).toHaveLength(0); // No complete lines yet
|
|
241
|
+
expect(state.getPending()).not.toBeNull();
|
|
242
|
+
|
|
243
|
+
// Process chunk 2 (newline terminated)
|
|
244
|
+
const lines2 = state.processChunk(chunk2, true);
|
|
245
|
+
expect(lines2).toHaveLength(1);
|
|
246
|
+
// The full text including Chinese should be preserved
|
|
247
|
+
expect(lines2[0]).toContain("Hello");
|
|
248
|
+
expect(lines2[0]).toContain("中文");
|
|
249
|
+
expect(lines2[0]).toContain("world");
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
it("handles chunk split in middle of 3-byte UTF-8 sequence", () => {
|
|
253
|
+
// Test specifically the bug case: ���文 → proper 中文
|
|
254
|
+
const fullText = '{"text": "test 日本語"}\n';
|
|
255
|
+
const fullBuffer = Buffer.from(fullText, "utf8");
|
|
256
|
+
|
|
257
|
+
// 日 = E6 97 A5 (3 bytes), 本 = E6 9C AC (3 bytes), 語 = E8 87 AA (3 bytes)
|
|
258
|
+
// Split after byte 10 (somewhere in 日本語)
|
|
259
|
+
const splitPoint = 10;
|
|
260
|
+
|
|
261
|
+
const chunk1 = fullBuffer.subarray(0, splitPoint);
|
|
262
|
+
const chunk2 = fullBuffer.subarray(splitPoint);
|
|
263
|
+
|
|
264
|
+
const result1 = simulateByteBuffering([chunk1], [false]);
|
|
265
|
+
expect(result1.completeLines).toHaveLength(0);
|
|
266
|
+
|
|
267
|
+
const result2 = simulateByteBuffering([chunk2], [true]);
|
|
268
|
+
expect(result2.completeLines).toHaveLength(1);
|
|
269
|
+
// Should NOT have replacement character (U+FFFD)
|
|
270
|
+
expect(result2.completeLines[0]).not.toContain("\uFFFD");
|
|
271
|
+
expect(result2.completeLines[0]).toContain("日本語");
|
|
272
|
+
});
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
// ---------------------------------------------------------------------------
|
|
276
|
+
// Partial JSON Line Preservation
|
|
277
|
+
// ---------------------------------------------------------------------------
|
|
278
|
+
|
|
279
|
+
describe("Partial JSON line preservation", () => {
|
|
280
|
+
function simulatePartialLineBuffering(readResults: {text: string; endsWithNewline: boolean}[]): {
|
|
281
|
+
completeLines: string[];
|
|
282
|
+
pendingText: string;
|
|
283
|
+
} {
|
|
284
|
+
let pendingText = "";
|
|
285
|
+
|
|
286
|
+
for (const read of readResults) {
|
|
287
|
+
const fullText = pendingText + read.text;
|
|
288
|
+
const lines = fullText.split("\n");
|
|
289
|
+
|
|
290
|
+
if (read.endsWithNewline) {
|
|
291
|
+
// All lines complete
|
|
292
|
+
pendingText = "";
|
|
293
|
+
return {
|
|
294
|
+
completeLines: lines.filter(l => l.trim()),
|
|
295
|
+
pendingText,
|
|
296
|
+
};
|
|
297
|
+
} else {
|
|
298
|
+
// Last line is incomplete
|
|
299
|
+
pendingText = lines.pop() ?? "";
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
return { completeLines: [], pendingText };
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
it("waits for complete line before emitting", () => {
|
|
307
|
+
// Simulate: partial JSON written, then newline, then next record
|
|
308
|
+
const read1 = { text: '{"ordinal": 1, "text": "part', endsWithNewline: false };
|
|
309
|
+
const read2 = { text: 'ial record"}\n', endsWithNewline: true };
|
|
310
|
+
|
|
311
|
+
const result = simulatePartialLineBuffering([read1, read2]);
|
|
312
|
+
|
|
313
|
+
expect(result.completeLines).toHaveLength(1);
|
|
314
|
+
expect(result.completeLines[0]).toContain("partial record");
|
|
315
|
+
expect(result.completeLines[0]).toContain('"ordinal": 1');
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
it("preserves multiple incomplete reads", () => {
|
|
319
|
+
const read1 = { text: '{"ordinal": 1', endsWithNewline: false };
|
|
320
|
+
const read2 = { text: ', "text": "', endsWithNewline: false };
|
|
321
|
+
const read3 = { text: 'hello"}\n', endsWithNewline: true };
|
|
322
|
+
|
|
323
|
+
const result = simulatePartialLineBuffering([read1, read2, read3]);
|
|
324
|
+
|
|
325
|
+
expect(result.completeLines).toHaveLength(1);
|
|
326
|
+
expect(result.completeLines[0]).toContain("hello");
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
it("handles empty pending text correctly", () => {
|
|
330
|
+
const read = { text: '{"ordinal": 1, "text": "complete"}\n', endsWithNewline: true };
|
|
331
|
+
|
|
332
|
+
const result = simulatePartialLineBuffering([read]);
|
|
333
|
+
|
|
334
|
+
expect(result.completeLines).toHaveLength(1);
|
|
335
|
+
expect(result.pendingText).toBe("");
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
it("does not emit incomplete lines", () => {
|
|
339
|
+
const read1 = { text: '{"incomplete": true}', endsWithNewline: false };
|
|
340
|
+
|
|
341
|
+
const result = simulatePartialLineBuffering([read1]);
|
|
342
|
+
|
|
343
|
+
expect(result.completeLines).toHaveLength(0);
|
|
344
|
+
expect(result.pendingText).toBe('{"incomplete": true}');
|
|
345
|
+
});
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
// ---------------------------------------------------------------------------
|
|
349
|
+
// Pending Delivery Queue Logic
|
|
350
|
+
// ---------------------------------------------------------------------------
|
|
351
|
+
|
|
352
|
+
describe("Pending delivery queue", () => {
|
|
353
|
+
interface DeliveryItem {
|
|
354
|
+
ordinal: number;
|
|
355
|
+
text: string;
|
|
356
|
+
delivered: boolean;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function simulateDeliveryQueue(
|
|
360
|
+
events: Array<{
|
|
361
|
+
type: "new_message" | "poll_retry" | "delivered";
|
|
362
|
+
ordinal?: number;
|
|
363
|
+
text?: string;
|
|
364
|
+
deliverySucceeds?: boolean;
|
|
365
|
+
}>
|
|
366
|
+
): {
|
|
367
|
+
queue: DeliveryItem[];
|
|
368
|
+
deliveryAttempts: number[];
|
|
369
|
+
} {
|
|
370
|
+
const queue: DeliveryItem[] = [];
|
|
371
|
+
const deliveryAttempts: number[] = [];
|
|
372
|
+
|
|
373
|
+
for (const event of events) {
|
|
374
|
+
if (event.type === "new_message" && event.ordinal !== undefined) {
|
|
375
|
+
queue.push({
|
|
376
|
+
ordinal: event.ordinal,
|
|
377
|
+
text: event.text ?? "",
|
|
378
|
+
delivered: false,
|
|
379
|
+
});
|
|
380
|
+
} else if (event.type === "poll_retry") {
|
|
381
|
+
for (const item of queue) {
|
|
382
|
+
if (!item.delivered) {
|
|
383
|
+
deliveryAttempts.push(item.ordinal);
|
|
384
|
+
if (event.deliverySucceeds) {
|
|
385
|
+
item.delivered = true;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
// Clean up delivered
|
|
390
|
+
while (queue.length > 0 && queue[0].delivered) {
|
|
391
|
+
queue.shift();
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
return { queue, deliveryAttempts };
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
it("queues message on delivery failure", () => {
|
|
400
|
+
const result = simulateDeliveryQueue([
|
|
401
|
+
{ type: "new_message", ordinal: 1, text: "test" },
|
|
402
|
+
]);
|
|
403
|
+
|
|
404
|
+
expect(result.queue).toHaveLength(1);
|
|
405
|
+
expect(result.queue[0].delivered).toBe(false);
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
it("retries queued messages on next poll", () => {
|
|
409
|
+
const result = simulateDeliveryQueue([
|
|
410
|
+
{ type: "new_message", ordinal: 1, text: "test" },
|
|
411
|
+
{ type: "poll_retry", deliverySucceeds: true },
|
|
412
|
+
]);
|
|
413
|
+
|
|
414
|
+
expect(result.deliveryAttempts).toContain(1);
|
|
415
|
+
expect(result.queue).toHaveLength(0); // Cleaned up
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
it("retries until successful", () => {
|
|
419
|
+
const result = simulateDeliveryQueue([
|
|
420
|
+
{ type: "new_message", ordinal: 1, text: "test" },
|
|
421
|
+
{ type: "poll_retry", deliverySucceeds: false },
|
|
422
|
+
{ type: "poll_retry", deliverySucceeds: false },
|
|
423
|
+
{ type: "poll_retry", deliverySucceeds: true },
|
|
424
|
+
]);
|
|
425
|
+
|
|
426
|
+
expect(result.deliveryAttempts).toEqual([1, 1, 1]); // 3 attempts
|
|
427
|
+
expect(result.queue).toHaveLength(0);
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
it("processes multiple queued messages in order", () => {
|
|
431
|
+
const result = simulateDeliveryQueue([
|
|
432
|
+
{ type: "new_message", ordinal: 1, text: "first" },
|
|
433
|
+
{ type: "new_message", ordinal: 2, text: "second" },
|
|
434
|
+
{ type: "poll_retry", deliverySucceeds: true }, // delivers both
|
|
435
|
+
]);
|
|
436
|
+
|
|
437
|
+
expect(result.deliveryAttempts).toContain(1);
|
|
438
|
+
expect(result.deliveryAttempts).toContain(2);
|
|
439
|
+
expect(result.queue).toHaveLength(0);
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
it("does not re-queue already queued messages", () => {
|
|
443
|
+
// This simulates the case where a message fails but is already in queue
|
|
444
|
+
// It should not be added twice
|
|
445
|
+
const result = simulateDeliveryQueue([
|
|
446
|
+
{ type: "new_message", ordinal: 1, text: "test" },
|
|
447
|
+
{ type: "poll_retry", deliverySucceeds: false },
|
|
448
|
+
]);
|
|
449
|
+
|
|
450
|
+
expect(result.queue).toHaveLength(1); // Still only one
|
|
451
|
+
expect(result.deliveryAttempts).toEqual([1]);
|
|
452
|
+
});
|
|
453
|
+
});
|