pi-harness-runtime 1.1.95 → 1.1.97
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 +33 -0
- package/harness/loop-runtime.ts +3 -0
- package/index.ts +130 -29
- package/package.json +2 -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/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/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/qdrant-skills/dist/client.d.ts +51 -0
- package/packages/qdrant-skills/dist/client.d.ts.map +1 -0
- package/packages/qdrant-skills/dist/client.js +105 -0
- package/packages/qdrant-skills/dist/client.js.map +1 -0
- package/packages/qdrant-skills/dist/embedder.d.ts +39 -0
- package/packages/qdrant-skills/dist/embedder.d.ts.map +1 -0
- package/packages/qdrant-skills/dist/embedder.js +101 -0
- package/packages/qdrant-skills/dist/embedder.js.map +1 -0
- package/packages/qdrant-skills/dist/index.d.ts +25 -0
- package/packages/qdrant-skills/dist/index.d.ts.map +1 -0
- package/packages/qdrant-skills/dist/index.js +25 -0
- package/packages/qdrant-skills/dist/index.js.map +1 -0
- package/packages/qdrant-skills/package.json +27 -0
- package/packages/qdrant-skills/src/client.ts +178 -0
- package/packages/qdrant-skills/src/embedder.ts +137 -0
- package/packages/qdrant-skills/src/index.ts +42 -0
- 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/dist/index.d.ts +40 -0
- package/packages/skills/dist/index.d.ts.map +1 -0
- package/packages/skills/dist/index.js +84 -0
- package/packages/skills/dist/index.js.map +1 -0
- package/packages/skills/dist/linter.d.ts +38 -0
- package/packages/skills/dist/linter.d.ts.map +1 -0
- package/packages/skills/dist/linter.js +323 -0
- package/packages/skills/dist/linter.js.map +1 -0
- package/packages/skills/dist/loader.d.ts +74 -0
- package/packages/skills/dist/loader.d.ts.map +1 -0
- package/packages/skills/dist/loader.js +280 -0
- package/packages/skills/dist/loader.js.map +1 -0
- package/packages/skills/dist/parser.d.ts +32 -0
- package/packages/skills/dist/parser.d.ts.map +1 -0
- package/packages/skills/dist/parser.js +459 -0
- package/packages/skills/dist/parser.js.map +1 -0
- package/packages/skills/dist/scanner.d.ts +33 -0
- package/packages/skills/dist/scanner.d.ts.map +1 -0
- package/packages/skills/dist/scanner.js +257 -0
- package/packages/skills/dist/scanner.js.map +1 -0
- package/packages/skills/dist/skill-commands.d.ts +33 -0
- package/packages/skills/dist/skill-commands.d.ts.map +1 -0
- package/packages/skills/dist/skill-commands.js +239 -0
- package/packages/skills/dist/skill-commands.js.map +1 -0
- package/packages/skills/dist/skill-tool.d.ts +120 -0
- package/packages/skills/dist/skill-tool.d.ts.map +1 -0
- package/packages/skills/dist/skill-tool.js +239 -0
- package/packages/skills/dist/skill-tool.js.map +1 -0
- package/packages/skills/dist/skill-versioning.d.ts +123 -0
- package/packages/skills/dist/skill-versioning.d.ts.map +1 -0
- package/packages/skills/dist/skill-versioning.js +240 -0
- package/packages/skills/dist/skill-versioning.js.map +1 -0
- package/packages/skills/dist/types.d.ts +226 -0
- package/packages/skills/dist/types.d.ts.map +1 -0
- package/packages/skills/dist/types.js +21 -0
- package/packages/skills/dist/types.js.map +1 -0
- package/packages/skills/package.json +46 -0
- package/packages/skills/src/index.ts +95 -0
- package/packages/skills/src/linter.ts +371 -0
- package/packages/skills/src/loader.ts +335 -0
- package/packages/skills/src/parser.ts +548 -0
- package/packages/skills/src/scanner.ts +322 -0
- package/packages/skills/src/skill-commands.ts +318 -0
- package/packages/skills/src/skill-tool.ts +324 -0
- package/packages/skills/src/skill-versioning.ts +345 -0
- package/packages/skills/src/types.ts +259 -0
- package/packages/skills/test/init-skills.test.ts +118 -0
- 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/trajectory/dist/src/curator.d.ts +189 -0
- package/packages/trajectory/dist/src/curator.d.ts.map +1 -0
- package/packages/trajectory/dist/src/curator.js +340 -0
- package/packages/trajectory/dist/src/curator.js.map +1 -0
- package/packages/trajectory/dist/src/journey-renderer.d.ts +53 -0
- package/packages/trajectory/dist/src/journey-renderer.d.ts.map +1 -0
- package/packages/trajectory/dist/src/journey-renderer.js +324 -0
- package/packages/trajectory/dist/src/journey-renderer.js.map +1 -0
- package/packages/trajectory/dist/src/learning-graph.d.ts +159 -0
- package/packages/trajectory/dist/src/learning-graph.d.ts.map +1 -0
- package/packages/trajectory/dist/src/learning-graph.js +258 -0
- package/packages/trajectory/dist/src/learning-graph.js.map +1 -0
- package/packages/trajectory/dist/src/memory-store.d.ts +146 -0
- package/packages/trajectory/dist/src/memory-store.d.ts.map +1 -0
- package/packages/trajectory/dist/src/memory-store.js +472 -0
- package/packages/trajectory/dist/src/memory-store.js.map +1 -0
- package/packages/trajectory/dist/src/review-fork.d.ts +164 -0
- package/packages/trajectory/dist/src/review-fork.d.ts.map +1 -0
- package/packages/trajectory/dist/src/review-fork.js +332 -0
- package/packages/trajectory/dist/src/review-fork.js.map +1 -0
- package/packages/trajectory/dist/src/write-approval.d.ts +136 -0
- package/packages/trajectory/dist/src/write-approval.d.ts.map +1 -0
- package/packages/trajectory/dist/src/write-approval.js +281 -0
- package/packages/trajectory/dist/src/write-approval.js.map +1 -0
- package/packages/trajectory/package.json +1 -1
- package/packages/trajectory/src/curator.ts +485 -0
- package/packages/trajectory/src/journey-renderer.ts +399 -0
- package/packages/trajectory/src/learning-graph.ts +363 -0
- package/packages/trajectory/src/memory-store.ts +578 -0
- package/packages/trajectory/src/review-fork.ts +450 -0
- package/packages/trajectory/src/write-approval.ts +372 -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/dist/blackboard.d.ts +98 -0
- package/packages/write-review/dist/blackboard.d.ts.map +1 -0
- package/packages/write-review/dist/blackboard.js +289 -0
- package/packages/write-review/dist/blackboard.js.map +1 -0
- package/packages/write-review/dist/event-bus.d.ts +106 -0
- package/packages/write-review/dist/event-bus.d.ts.map +1 -0
- package/packages/write-review/dist/event-bus.js +294 -0
- package/packages/write-review/dist/event-bus.js.map +1 -0
- package/packages/write-review/dist/gate.d.ts +37 -0
- package/packages/write-review/dist/gate.d.ts.map +1 -0
- package/packages/write-review/dist/gate.js +138 -0
- package/packages/write-review/dist/gate.js.map +1 -0
- package/packages/write-review/dist/index.d.ts +22 -0
- package/packages/write-review/dist/index.d.ts.map +1 -0
- package/packages/write-review/dist/index.js +60 -0
- package/packages/write-review/dist/index.js.map +1 -0
- package/packages/write-review/dist/injection.d.ts +40 -0
- package/packages/write-review/dist/injection.d.ts.map +1 -0
- package/packages/write-review/dist/injection.js +179 -0
- package/packages/write-review/dist/injection.js.map +1 -0
- package/packages/write-review/dist/review.d.ts +54 -0
- package/packages/write-review/dist/review.d.ts.map +1 -0
- package/packages/write-review/dist/review.js +154 -0
- package/packages/write-review/dist/review.js.map +1 -0
- package/packages/write-review/dist/trigger.d.ts +39 -0
- package/packages/write-review/dist/trigger.d.ts.map +1 -0
- package/packages/write-review/dist/trigger.js +107 -0
- package/packages/write-review/dist/trigger.js.map +1 -0
- package/packages/write-review/dist/types.d.ts +78 -0
- package/packages/write-review/dist/types.d.ts.map +1 -0
- package/packages/write-review/dist/types.js +26 -0
- package/packages/write-review/dist/types.js.map +1 -0
- package/packages/write-review/package.json +1 -1
- package/packages/write-review/src/event-bus.ts +351 -0
- package/packages/write-review/src/index.ts +1 -0
- package/packages/write-review/src/injection.ts +25 -3
- package/proactive-compact.ts +51 -7
- package/scripts/sync-skills.ts +275 -0
- package/scripts/sync-to-qdrant.ts +398 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAwEH;;GAEG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG;IAC1B,MAAM,EAAE,QAAQ,EAAE,oCAAoC;IACtD,QAAQ,EAAE,UAAU,EAAE,gCAAgC;IACtD,MAAM,EAAE,QAAQ,EAAE,+BAA+B;CACxC,CAAC;AAIX;;GAEG;AACH,MAAM,UAAU,eAAe,CAAC,KAAkB;IACjD,OAAO,KAAK,KAAK,UAAU,IAAI,KAAK,KAAK,SAAS,CAAC;AACpD,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,KAAkB;IAC7C,OAAO,KAAK,KAAK,gBAAgB,IAAI,KAAK,KAAK,WAAW,CAAC;AAC5D,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,KAAkB;IAC1C,OAAO,CACN,KAAK,KAAK,MAAM,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,mBAAmB,CACxE,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ping-Pong Event Bus
|
|
3
|
+
*
|
|
4
|
+
* Lightweight event bus for two-agent coordination.
|
|
5
|
+
* Watches blackboard status.json for state changes.
|
|
6
|
+
* Emits events and notifies agents via ExtensionAPI.
|
|
7
|
+
*
|
|
8
|
+
* NO AI NEEDED - deterministic file watching + state machine.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { existsSync, readFileSync, watch } from "node:fs";
|
|
12
|
+
import { join } from "node:path";
|
|
13
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Ping-Pong Events
|
|
17
|
+
*/
|
|
18
|
+
export enum PingPongEvent {
|
|
19
|
+
SESSION_STARTED = "session_started",
|
|
20
|
+
WRITING_STARTED = "writing_started",
|
|
21
|
+
WRITER_DONE = "writer_done",
|
|
22
|
+
REVIEW_NEEDED = "review_needed",
|
|
23
|
+
REVIEW_STARTED = "review_started",
|
|
24
|
+
REVIEW_COMPLETE = "review_complete",
|
|
25
|
+
APPROVED = "approved",
|
|
26
|
+
CHANGES_REQUESTED = "changes_requested",
|
|
27
|
+
BLOCKED = "blocked",
|
|
28
|
+
SESSION_ENDED = "session_ended",
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Event payload
|
|
33
|
+
*/
|
|
34
|
+
export interface PingPongEventPayload {
|
|
35
|
+
event: PingPongEvent;
|
|
36
|
+
phase: string;
|
|
37
|
+
iteration: number;
|
|
38
|
+
timestamp: string;
|
|
39
|
+
data?: Record<string, unknown>;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Event listener callback
|
|
44
|
+
*/
|
|
45
|
+
export type PingPongListener = (payload: PingPongEventPayload) => void | Promise<void>;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Phase to event mapping
|
|
49
|
+
*/
|
|
50
|
+
const PHASE_TO_EVENT: Record<string, PingPongEvent> = {
|
|
51
|
+
idle: PingPongEvent.SESSION_STARTED,
|
|
52
|
+
writing: PingPongEvent.WRITING_STARTED,
|
|
53
|
+
pending_review: PingPongEvent.REVIEW_NEEDED,
|
|
54
|
+
reviewing: PingPongEvent.REVIEW_STARTED,
|
|
55
|
+
approved: PingPongEvent.APPROVED,
|
|
56
|
+
blocked: PingPongEvent.BLOCKED,
|
|
57
|
+
changes_requested: PingPongEvent.CHANGES_REQUESTED,
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Simple Event Bus for ping-pong coordination
|
|
62
|
+
*/
|
|
63
|
+
export class PingPongEventBus {
|
|
64
|
+
private listeners: Map<PingPongEvent, Set<PingPongListener>> = new Map();
|
|
65
|
+
private watchers: Map<string, ReturnType<typeof watch>> = new Map();
|
|
66
|
+
private lastStatusHash: string | null = null;
|
|
67
|
+
private pi: ExtensionAPI | null = null;
|
|
68
|
+
private projectPath: string | null = null;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Register an event listener
|
|
72
|
+
*/
|
|
73
|
+
on(event: PingPongEvent, listener: PingPongListener): () => void {
|
|
74
|
+
if (!this.listeners.has(event)) {
|
|
75
|
+
this.listeners.set(event, new Set());
|
|
76
|
+
}
|
|
77
|
+
this.listeners.get(event)!.add(listener);
|
|
78
|
+
|
|
79
|
+
// Return unsubscribe function
|
|
80
|
+
return () => {
|
|
81
|
+
this.listeners.get(event)?.delete(listener);
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Register a listener for all events
|
|
87
|
+
*/
|
|
88
|
+
onAny(listener: PingPongListener): () => void {
|
|
89
|
+
const wrappedListener = (payload: PingPongEventPayload) => {
|
|
90
|
+
return listener(payload);
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
// Listen to all events
|
|
94
|
+
for (const event of Object.values(PingPongEvent)) {
|
|
95
|
+
this.on(event, wrappedListener);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Return unsubscribe for all
|
|
99
|
+
return () => {
|
|
100
|
+
for (const event of Object.values(PingPongEvent)) {
|
|
101
|
+
this.listeners.get(event)?.delete(wrappedListener);
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Emit an event
|
|
108
|
+
*/
|
|
109
|
+
private emit(event: PingPongEvent, payload: Omit<PingPongEventPayload, "event">): void {
|
|
110
|
+
const fullPayload: PingPongEventPayload = {
|
|
111
|
+
event,
|
|
112
|
+
...payload,
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
// Call specific listeners
|
|
116
|
+
const listeners = this.listeners.get(event);
|
|
117
|
+
if (listeners) {
|
|
118
|
+
for (const listener of listeners) {
|
|
119
|
+
try {
|
|
120
|
+
const result = listener(fullPayload);
|
|
121
|
+
if (result instanceof Promise) {
|
|
122
|
+
result.catch((err) => {
|
|
123
|
+
console.error(`[PingPong] Listener error:`, err);
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
} catch (err) {
|
|
127
|
+
console.error(`[PingPong] Listener error:`, err);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Notify via ExtensionAPI if available
|
|
133
|
+
if (this.pi) {
|
|
134
|
+
this.notifyAgents(fullPayload);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Log for debugging
|
|
138
|
+
console.error(
|
|
139
|
+
`[PingPong] Event: ${event}, phase=${payload.phase}, iteration=${payload.iteration}`
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Notify agents via ExtensionAPI
|
|
145
|
+
*/
|
|
146
|
+
private notifyAgents(payload: PingPongEventPayload): void {
|
|
147
|
+
if (!this.pi) return;
|
|
148
|
+
|
|
149
|
+
const { event, phase, iteration } = payload;
|
|
150
|
+
|
|
151
|
+
// Map events to user-facing notifications
|
|
152
|
+
const messages: Record<PingPongEvent, string> = {
|
|
153
|
+
[PingPongEvent.SESSION_STARTED]: `Write-review session started`,
|
|
154
|
+
[PingPongEvent.WRITING_STARTED]: `Writer agent started (iteration ${iteration})`,
|
|
155
|
+
[PingPongEvent.WRITER_DONE]: `Writer done! Code ready for review`,
|
|
156
|
+
[PingPongEvent.REVIEW_NEEDED]: `Review needed - iteration ${iteration}`,
|
|
157
|
+
[PingPongEvent.REVIEW_STARTED]: `Reviewer agent started`,
|
|
158
|
+
[PingPongEvent.REVIEW_COMPLETE]: `Review complete - verdict: ${phase}`,
|
|
159
|
+
[PingPongEvent.APPROVED]: `APPROVED! Code ready to build/commit`,
|
|
160
|
+
[PingPongEvent.CHANGES_REQUESTED]: `Changes requested - iteration ${iteration}`,
|
|
161
|
+
[PingPongEvent.BLOCKED]: `BLOCKED - human intervention needed`,
|
|
162
|
+
[PingPongEvent.SESSION_ENDED]: `Write-review session ended`,
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
const message = messages[event];
|
|
166
|
+
if (message) {
|
|
167
|
+
const level = event === PingPongEvent.APPROVED
|
|
168
|
+
? "success"
|
|
169
|
+
: event === PingPongEvent.BLOCKED
|
|
170
|
+
? "error"
|
|
171
|
+
: "info";
|
|
172
|
+
|
|
173
|
+
// Use ui.notify if available on ExtensionContext
|
|
174
|
+
try {
|
|
175
|
+
// SAFETY: ExtensionAPI doesn't export ui type, we access it via dynamic lookup
|
|
176
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
177
|
+
const ui = (this.pi as any)?.ui;
|
|
178
|
+
if (ui?.notify) {
|
|
179
|
+
ui.notify(message, level);
|
|
180
|
+
}
|
|
181
|
+
} catch {
|
|
182
|
+
// ExtensionAPI ui.notify not available
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Start watching a blackboard status.json
|
|
189
|
+
*/
|
|
190
|
+
startWatching(blackboardPath: string): void {
|
|
191
|
+
// Stop any existing watcher
|
|
192
|
+
this.stopWatching();
|
|
193
|
+
|
|
194
|
+
const statusPath = join(blackboardPath, "status.json");
|
|
195
|
+
this.projectPath = blackboardPath.replace("/.write-review", "");
|
|
196
|
+
|
|
197
|
+
if (!existsSync(statusPath)) {
|
|
198
|
+
console.error(`[PingPong] status.json not found at ${statusPath}`);
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Read initial status
|
|
203
|
+
this.readAndEmit(statusPath);
|
|
204
|
+
|
|
205
|
+
// Watch for changes
|
|
206
|
+
try {
|
|
207
|
+
const watcher = watch(blackboardPath, { persistent: true }, (_eventType, filename) => {
|
|
208
|
+
if (filename === "status.json") {
|
|
209
|
+
this.readAndEmit(statusPath);
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
this.watchers.set(statusPath, watcher);
|
|
214
|
+
console.error(`[PingPong] Started watching ${blackboardPath}`);
|
|
215
|
+
} catch (err) {
|
|
216
|
+
console.error(`[PingPong] Failed to watch ${statusPath}:`, err);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Read status.json and emit events if changed
|
|
222
|
+
*/
|
|
223
|
+
private readAndEmit(statusPath: string): void {
|
|
224
|
+
try {
|
|
225
|
+
const content = readFileSync(statusPath, "utf-8");
|
|
226
|
+
const status = JSON.parse(content);
|
|
227
|
+
|
|
228
|
+
// Check if status changed
|
|
229
|
+
const statusHash = JSON.stringify({
|
|
230
|
+
phase: status.phase,
|
|
231
|
+
writerDone: status.writerDone,
|
|
232
|
+
iteration: status.iteration,
|
|
233
|
+
verdict: status.verdict,
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
if (statusHash === this.lastStatusHash) {
|
|
237
|
+
return; // No change
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const previousHash = this.lastStatusHash;
|
|
241
|
+
this.lastStatusHash = statusHash;
|
|
242
|
+
|
|
243
|
+
// Emit events based on phase transition
|
|
244
|
+
const phase = status.phase as string;
|
|
245
|
+
const iteration = status.iteration || 1;
|
|
246
|
+
|
|
247
|
+
// Detect writer done (was false, now true)
|
|
248
|
+
const wasWriterDone = previousHash ? previousHash.includes('"writerDone":true') : false;
|
|
249
|
+
if (status.writerDone && !wasWriterDone) {
|
|
250
|
+
this.emit(PingPongEvent.WRITER_DONE, {
|
|
251
|
+
phase,
|
|
252
|
+
iteration,
|
|
253
|
+
timestamp: new Date().toISOString(),
|
|
254
|
+
data: { writerMessage: status.writerMessage },
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Emit phase event
|
|
259
|
+
const event = PHASE_TO_EVENT[phase];
|
|
260
|
+
if (event) {
|
|
261
|
+
this.emit(event, {
|
|
262
|
+
phase,
|
|
263
|
+
iteration,
|
|
264
|
+
timestamp: new Date().toISOString(),
|
|
265
|
+
data: { verdict: status.verdict, verdictMessage: status.verdictMessage },
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
} catch (err) {
|
|
270
|
+
console.error(`[PingPong] Failed to read status.json:`, err);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Stop watching
|
|
276
|
+
*/
|
|
277
|
+
stopWatching(): void {
|
|
278
|
+
for (const [path, watcher] of this.watchers) {
|
|
279
|
+
watcher.close();
|
|
280
|
+
this.watchers.delete(path);
|
|
281
|
+
}
|
|
282
|
+
console.error(`[PingPong] Stopped watching`);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Set ExtensionAPI for notifications
|
|
287
|
+
*/
|
|
288
|
+
setExtensionAPI(pi: ExtensionAPI): void {
|
|
289
|
+
this.pi = pi;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Get current status hash
|
|
294
|
+
*/
|
|
295
|
+
getLastStatusHash(): string | null {
|
|
296
|
+
return this.lastStatusHash;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Clean up
|
|
301
|
+
*/
|
|
302
|
+
destroy(): void {
|
|
303
|
+
this.stopWatching();
|
|
304
|
+
this.listeners.clear();
|
|
305
|
+
this.pi = null;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Singleton instance
|
|
310
|
+
let globalEventBus: PingPongEventBus | null = null;
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Get global event bus instance
|
|
314
|
+
*/
|
|
315
|
+
export function getPingPongEventBus(): PingPongEventBus {
|
|
316
|
+
if (!globalEventBus) {
|
|
317
|
+
globalEventBus = new PingPongEventBus();
|
|
318
|
+
}
|
|
319
|
+
return globalEventBus;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Create a new event bus instance
|
|
324
|
+
*/
|
|
325
|
+
export function createPingPongEventBus(): PingPongEventBus {
|
|
326
|
+
return new PingPongEventBus();
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Helper: Check if status changed from previous
|
|
331
|
+
*/
|
|
332
|
+
export function hasStatusChanged(
|
|
333
|
+
current: Record<string, unknown>,
|
|
334
|
+
previous: string | null
|
|
335
|
+
): boolean {
|
|
336
|
+
const currentHash = JSON.stringify({
|
|
337
|
+
phase: current.phase,
|
|
338
|
+
writerDone: current.writerDone,
|
|
339
|
+
iteration: current.iteration,
|
|
340
|
+
verdict: current.verdict,
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
return currentHash !== previous;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Helper: Format event for logging
|
|
348
|
+
*/
|
|
349
|
+
export function formatPingPongEvent(event: PingPongEventPayload): string {
|
|
350
|
+
return `[PingPong:${event.event}] phase=${event.phase}, iteration=${event.iteration}, time=${event.timestamp}`;
|
|
351
|
+
}
|
|
@@ -113,7 +113,9 @@ export function getWriteReviewHints(projectPath: string): {
|
|
|
113
113
|
* Register write-review extension with pi
|
|
114
114
|
*/
|
|
115
115
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
116
|
+
import type { BeforeAgentStartEventResult } from "@earendil-works/pi-coding-agent";
|
|
116
117
|
import { createBlackboard } from "./blackboard.js";
|
|
118
|
+
import { getPingPongEventBus, PingPongEvent } from "./event-bus.js";
|
|
117
119
|
|
|
118
120
|
export function injectWriterInstructions(
|
|
119
121
|
pi: ExtensionAPI,
|
|
@@ -123,8 +125,25 @@ export function injectWriterInstructions(
|
|
|
123
125
|
let blackboard: ReturnType<typeof createBlackboard> | null = null;
|
|
124
126
|
let hintsInjected = false;
|
|
125
127
|
|
|
128
|
+
// Initialize event bus with ExtensionAPI
|
|
129
|
+
const eventBus = getPingPongEventBus();
|
|
130
|
+
eventBus.setExtensionAPI(pi);
|
|
131
|
+
|
|
132
|
+
// Register event listeners for debugging
|
|
133
|
+
eventBus.on(PingPongEvent.WRITER_DONE, (payload) => {
|
|
134
|
+
if (config?.debug) {
|
|
135
|
+
console.log(`[write-review] Writer done: ${JSON.stringify(payload)}`);
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
eventBus.on(PingPongEvent.REVIEW_NEEDED, (payload) => {
|
|
140
|
+
if (config?.debug) {
|
|
141
|
+
console.log(`[write-review] Review needed: ${JSON.stringify(payload)}`);
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
|
|
126
145
|
// System prompt injection
|
|
127
|
-
pi.on("before_agent_start", async (event) => {
|
|
146
|
+
pi.on("before_agent_start", async (event): Promise<BeforeAgentStartEventResult | undefined> => {
|
|
128
147
|
const cwd = findProjectRoot(process.cwd?.() ?? "/");
|
|
129
148
|
if (cwd !== currentProject) {
|
|
130
149
|
currentProject = cwd;
|
|
@@ -135,9 +154,10 @@ export function injectWriterInstructions(
|
|
|
135
154
|
if (!hintsInjected && blackboard) {
|
|
136
155
|
const status = blackboard.load();
|
|
137
156
|
if (status && status.phase !== "idle") {
|
|
138
|
-
event.systemPrompt
|
|
157
|
+
let systemPrompt = event.systemPrompt;
|
|
158
|
+
systemPrompt += formatWriteReviewHint(cwd);
|
|
139
159
|
if (status.phase !== "approved") {
|
|
140
|
-
|
|
160
|
+
systemPrompt += formatReviewReminder(cwd);
|
|
141
161
|
}
|
|
142
162
|
hintsInjected = true;
|
|
143
163
|
if (config?.debug) {
|
|
@@ -145,8 +165,10 @@ export function injectWriterInstructions(
|
|
|
145
165
|
`[write-review] Hints injected for ${cwd}, phase: ${status.phase}`,
|
|
146
166
|
);
|
|
147
167
|
}
|
|
168
|
+
return { systemPrompt };
|
|
148
169
|
}
|
|
149
170
|
}
|
|
171
|
+
return undefined;
|
|
150
172
|
});
|
|
151
173
|
|
|
152
174
|
// Build gate
|
package/proactive-compact.ts
CHANGED
|
@@ -7,7 +7,7 @@ export const MAX_PROACTIVE_COMPACT_FAILURES = 3;
|
|
|
7
7
|
export const OUTPUT_LIMIT_AUTO_RESUME_LIMIT = 3;
|
|
8
8
|
export const OUTPUT_LIMIT_RESUME_PROMPT =
|
|
9
9
|
"Output token limit hit. Resume directly — no apology, no recap. Pick up mid-thought if the cut happened there. Break remaining work into smaller pieces.";
|
|
10
|
-
export const PROVIDER_OVERLOAD_AUTO_RESUME_LIMIT =
|
|
10
|
+
export const PROVIDER_OVERLOAD_AUTO_RESUME_LIMIT = 100;
|
|
11
11
|
export const PROVIDER_OVERLOAD_AUTO_RESUME_MIN_MS = 60_000;
|
|
12
12
|
export const PROVIDER_OVERLOAD_AUTO_RESUME_MAX_MS = 5 * 60_000;
|
|
13
13
|
export const PROVIDER_OVERLOAD_RESUME_PROMPT = "resume";
|
|
@@ -104,6 +104,43 @@ export function shouldQueueOutputLimitResume(
|
|
|
104
104
|
);
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
+
/**
|
|
108
|
+
* Check if this is a FRESH 529 overload error (not a "Retry failed" report)
|
|
109
|
+
* Fresh errors should trigger retries; "Retry failed" messages should NOT.
|
|
110
|
+
*/
|
|
111
|
+
export function isFreshProviderOverloadError(
|
|
112
|
+
message: AssistantStopLike,
|
|
113
|
+
): boolean {
|
|
114
|
+
if (message.role !== "assistant") {
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
const text = readAssistantStopText(message);
|
|
118
|
+
// Fresh overload: contains overload indicator + 529 code
|
|
119
|
+
const isFreshOverload =
|
|
120
|
+
text.includes("overloaded_error") ||
|
|
121
|
+
text.includes("peak-hour surge") ||
|
|
122
|
+
text.includes("temporarily busy") ||
|
|
123
|
+
text.includes("server is temporarily busy") ||
|
|
124
|
+
text.includes("try again shortly");
|
|
125
|
+
// But NOT if it's a "Retry failed" report (that means our retry already failed)
|
|
126
|
+
const isRetryFailedReport = /Retry failed after \d+ attempts:/i.test(text);
|
|
127
|
+
// Extract request_id for logging
|
|
128
|
+
const requestIdMatch = text.match(/request_id[^"]*"([^"]+)"/i) || text.match(/request_id[^"]*([a-f0-9]+)/i);
|
|
129
|
+
const requestId = requestIdMatch ? requestIdMatch[1] : "unknown";
|
|
130
|
+
if (isFreshOverload) {
|
|
131
|
+
console.error(
|
|
132
|
+
`[pi-harness] Fresh 529 detected (request=${requestId}), retry_failed_report=${isRetryFailedReport}`,
|
|
133
|
+
);
|
|
134
|
+
} else if (text.includes("529") || text.includes("overload")) {
|
|
135
|
+
console.error(`[pi-harness] 529-related but NOT fresh (stopReason or other): ${text.slice(0, 100)}`);
|
|
136
|
+
}
|
|
137
|
+
return isFreshOverload && !isRetryFailedReport;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Legacy check - returns true for BOTH fresh overloads AND "Retry failed" reports
|
|
142
|
+
* @deprecated Use isFreshProviderOverloadError for retry logic
|
|
143
|
+
*/
|
|
107
144
|
export function isProviderOverloadAssistantMessage(
|
|
108
145
|
message: AssistantStopLike,
|
|
109
146
|
): boolean {
|
|
@@ -111,7 +148,7 @@ export function isProviderOverloadAssistantMessage(
|
|
|
111
148
|
return false;
|
|
112
149
|
}
|
|
113
150
|
|
|
114
|
-
return /overloaded_error|peak-hour surge|temporarily busy|server is temporarily busy|try again shortly
|
|
151
|
+
return /overloaded_error|peak-hour surge|temporarily busy|server is temporarily busy|try again shortly|\b529\b|\b2064\b/i.test(
|
|
115
152
|
readAssistantStopText(message),
|
|
116
153
|
);
|
|
117
154
|
}
|
|
@@ -124,11 +161,18 @@ export function shouldQueueProviderOverloadResume(
|
|
|
124
161
|
): boolean {
|
|
125
162
|
const maxAttempts =
|
|
126
163
|
options?.maxAttempts ?? PROVIDER_OVERLOAD_AUTO_RESUME_LIMIT;
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
164
|
+
const isFresh = isFreshProviderOverloadError(message);
|
|
165
|
+
const underLimit = resumeAttempts < maxAttempts;
|
|
166
|
+
const noPending = !hasPendingMessages;
|
|
167
|
+
const shouldQueue = isFresh && underLimit && noPending;
|
|
168
|
+
|
|
169
|
+
if (isFresh) {
|
|
170
|
+
console.error(
|
|
171
|
+
`[pi-harness] shouldQueue=${shouldQueue} attempts=${resumeAttempts}/${maxAttempts} hasPending=${hasPendingMessages}`,
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return shouldQueue;
|
|
132
176
|
}
|
|
133
177
|
|
|
134
178
|
export function getProviderOverloadResumeDelayMs(options?: {
|