pi-harness-runtime 1.1.190 → 1.1.192
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 +9 -3
- 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/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.map +1 -1
- package/packages/notification/dist/adapters/telegram-adapter.js +12 -5
- package/packages/notification/dist/adapters/telegram-adapter.js.map +1 -1
- package/packages/notification/dist/base-adapter.d.ts +14 -0
- package/packages/notification/dist/base-adapter.d.ts.map +1 -1
- package/packages/notification/dist/base-adapter.js +13 -0
- package/packages/notification/dist/base-adapter.js.map +1 -1
- package/packages/notification/dist/conversation-bridge.d.ts +93 -0
- package/packages/notification/dist/conversation-bridge.d.ts.map +1 -0
- package/packages/notification/dist/conversation-bridge.js +294 -0
- package/packages/notification/dist/conversation-bridge.js.map +1 -0
- package/packages/notification/dist/index.d.ts +1 -0
- package/packages/notification/dist/index.d.ts.map +1 -1
- package/packages/notification/dist/index.js +1 -0
- package/packages/notification/dist/index.js.map +1 -1
- package/packages/notification/dist/notification-center.d.ts +5 -0
- package/packages/notification/dist/notification-center.d.ts.map +1 -1
- package/packages/notification/dist/notification-center.js +12 -0
- package/packages/notification/dist/notification-center.js.map +1 -1
- package/packages/notification/package.json +1 -1
- package/packages/notification/src/adapters/telegram-adapter.ts +16 -5
- package/packages/notification/src/base-adapter.ts +23 -1
- package/packages/notification/src/conversation-bridge.ts +385 -0
- package/packages/notification/src/index.ts +4 -0
- package/packages/notification/src/notification-center.ts +13 -0
- 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
|
@@ -69,17 +69,28 @@ return this._botUsername;
|
|
|
69
69
|
const response = await fetch(
|
|
70
70
|
`https://api.telegram.org/bot${cfg.botToken}/getMe`,
|
|
71
71
|
);
|
|
72
|
-
if (!response.ok)
|
|
72
|
+
if (!response.ok) {
|
|
73
|
+
console.warn(`[TelegramAdapter] getMe failed: HTTP ${response.status}`);
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
73
76
|
const data = (await response.json()) as { ok: boolean; result?: { username?: string } };
|
|
74
|
-
if (data.ok
|
|
75
|
-
|
|
77
|
+
if (!data.ok || !data.result?.username) {
|
|
78
|
+
console.warn(`[TelegramAdapter] getMe returned ok=false`);
|
|
79
|
+
return false;
|
|
76
80
|
}
|
|
81
|
+
|
|
82
|
+
this._botUsername = data.result.username;
|
|
83
|
+
this.markHealthy(); // Mark as healthy after successful getMe
|
|
84
|
+
console.log(`[TelegramAdapter] Bot @${this._botUsername} verified successfully`);
|
|
85
|
+
|
|
77
86
|
// Start polling for callback queries if handler is registered
|
|
78
87
|
if (this._callbackHandler) {
|
|
79
88
|
this.startPolling();
|
|
80
89
|
}
|
|
81
|
-
|
|
82
|
-
|
|
90
|
+
|
|
91
|
+
return true;
|
|
92
|
+
} catch (err) {
|
|
93
|
+
console.error(`[TelegramAdapter] Initialize failed:`, err);
|
|
83
94
|
return false;
|
|
84
95
|
}
|
|
85
96
|
}
|
|
@@ -28,9 +28,17 @@ export interface ChannelAdapter {
|
|
|
28
28
|
* Check if the adapter is properly configured
|
|
29
29
|
*/
|
|
30
30
|
isConfigured(): boolean;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Check if the adapter is healthy (initialized and working)
|
|
34
|
+
* Returns true if initialized successfully, false otherwise
|
|
35
|
+
*/
|
|
36
|
+
isHealthy(): boolean;
|
|
31
37
|
}
|
|
32
38
|
|
|
33
|
-
export abstract class BaseChannelAdapter implements ChannelAdapter {
|
|
39
|
+
export abstract class BaseChannelAdapter implements ChannelAdapter {
|
|
40
|
+
protected _healthy = false;
|
|
41
|
+
|
|
34
42
|
abstract readonly id: string;
|
|
35
43
|
abstract readonly type: string;
|
|
36
44
|
|
|
@@ -43,6 +51,20 @@ export abstract class BaseChannelAdapter implements ChannelAdapter {
|
|
|
43
51
|
return this.config.enabled;
|
|
44
52
|
}
|
|
45
53
|
|
|
54
|
+
/**
|
|
55
|
+
* Default health check - subclasses should override for actual health verification
|
|
56
|
+
*/
|
|
57
|
+
isHealthy(): boolean {
|
|
58
|
+
return this._healthy;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Mark adapter as healthy (called after successful initialize)
|
|
63
|
+
*/
|
|
64
|
+
protected markHealthy(): void {
|
|
65
|
+
this._healthy = true;
|
|
66
|
+
}
|
|
67
|
+
|
|
46
68
|
/**
|
|
47
69
|
* Redact sensitive data from payload before sending
|
|
48
70
|
*/
|
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Telegram Conversation Bridge
|
|
3
|
+
*
|
|
4
|
+
* Sends assistant responses and tool activity to Telegram.
|
|
5
|
+
* Hooks into Pi lifecycle events to capture:
|
|
6
|
+
* - message_end: Final assistant responses
|
|
7
|
+
* - agent_end: When agent finishes a turn
|
|
8
|
+
* - agent_settled: When agent fully settles
|
|
9
|
+
*
|
|
10
|
+
* Features:
|
|
11
|
+
* - Deduplication across events
|
|
12
|
+
* - Safe formatting
|
|
13
|
+
* - Delivery status tracking
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
17
|
+
import type { NotificationCenter } from "./notification-center.js";
|
|
18
|
+
import type { NotificationPayload } from "./types.js";
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Configuration for the conversation bridge
|
|
22
|
+
*/
|
|
23
|
+
export interface ConversationBridgeConfig {
|
|
24
|
+
/** Whether to send assistant final responses */
|
|
25
|
+
sendAssistantResponses: boolean;
|
|
26
|
+
/** Whether to send tool summaries */
|
|
27
|
+
sendToolSummaries: boolean;
|
|
28
|
+
/** Max message length before chunking */
|
|
29
|
+
maxMessageLength: number;
|
|
30
|
+
/** Minimum confidence to send a message */
|
|
31
|
+
minConfidence: number;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Pending message to prevent duplicate sends
|
|
36
|
+
*/
|
|
37
|
+
interface PendingMessage {
|
|
38
|
+
messageId: string;
|
|
39
|
+
content: string;
|
|
40
|
+
timestamp: number;
|
|
41
|
+
events: Set<string>; // Events that triggered this message
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Conversation Bridge
|
|
46
|
+
*
|
|
47
|
+
* Bridges Pi conversation events to Telegram notifications.
|
|
48
|
+
*/
|
|
49
|
+
export class ConversationBridge {
|
|
50
|
+
private pi: ExtensionAPI;
|
|
51
|
+
private center: NotificationCenter;
|
|
52
|
+
private config: ConversationBridgeConfig;
|
|
53
|
+
private pendingMessages: Map<string, PendingMessage> = new Map();
|
|
54
|
+
private deliveredMessageIds: Set<string> = new Set();
|
|
55
|
+
private sessionStartTime: number = Date.now();
|
|
56
|
+
private lastMessageId: string | null = null;
|
|
57
|
+
|
|
58
|
+
constructor(
|
|
59
|
+
pi: ExtensionAPI,
|
|
60
|
+
center: NotificationCenter,
|
|
61
|
+
config: Partial<ConversationBridgeConfig> = {},
|
|
62
|
+
) {
|
|
63
|
+
this.pi = pi;
|
|
64
|
+
this.center = center;
|
|
65
|
+
this.config = {
|
|
66
|
+
sendAssistantResponses: true,
|
|
67
|
+
sendToolSummaries: true,
|
|
68
|
+
maxMessageLength: 4000,
|
|
69
|
+
minConfidence: 0.5,
|
|
70
|
+
...config,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Register all lifecycle hooks
|
|
76
|
+
*/
|
|
77
|
+
register(): void {
|
|
78
|
+
console.log("[ConversationBridge] Registering lifecycle hooks...");
|
|
79
|
+
|
|
80
|
+
// Session start - reset state
|
|
81
|
+
this.pi.on("session_start", () => {
|
|
82
|
+
this.reset();
|
|
83
|
+
console.log("[ConversationBridge] Session started");
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
// Session compact - flush pending messages
|
|
87
|
+
this.pi.on("session_compact", (event) => {
|
|
88
|
+
console.log(
|
|
89
|
+
`[ConversationBridge] Compaction: ${event.reason} (willRetry=${event.willRetry})`,
|
|
90
|
+
);
|
|
91
|
+
this.flushPendingMessages();
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// Session shutdown - cleanup
|
|
95
|
+
this.pi.on("session_shutdown", () => {
|
|
96
|
+
console.log("[ConversationBridge] Session shutting down");
|
|
97
|
+
this.flushPendingMessages();
|
|
98
|
+
this.stop();
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
// Message start - track new message
|
|
102
|
+
this.pi.on("message_start", (event) => {
|
|
103
|
+
const msg = event.message as { id?: string; role?: string };
|
|
104
|
+
if (msg?.role === "assistant" && msg?.id) {
|
|
105
|
+
this.lastMessageId = msg.id;
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
// Message end - capture final assistant response
|
|
110
|
+
this.pi.on("message_end", (event) => {
|
|
111
|
+
const msg = event.message as { id?: string; role?: string; content?: unknown };
|
|
112
|
+
if (msg?.role !== "assistant") return;
|
|
113
|
+
|
|
114
|
+
const content = this.extractMessageContent(msg);
|
|
115
|
+
if (!content || content.length < 10) return; // Skip empty/too short
|
|
116
|
+
|
|
117
|
+
const messageId = msg.id ?? `msg-${Date.now()}`;
|
|
118
|
+
|
|
119
|
+
// Deduplicate
|
|
120
|
+
if (this.deliveredMessageIds.has(messageId)) {
|
|
121
|
+
console.log(`[ConversationBridge] Skipping duplicate: ${messageId}`);
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Check if already pending
|
|
126
|
+
let pending = this.pendingMessages.get(messageId);
|
|
127
|
+
if (!pending) {
|
|
128
|
+
pending = {
|
|
129
|
+
messageId,
|
|
130
|
+
content,
|
|
131
|
+
timestamp: Date.now(),
|
|
132
|
+
events: new Set(),
|
|
133
|
+
};
|
|
134
|
+
this.pendingMessages.set(messageId, pending);
|
|
135
|
+
}
|
|
136
|
+
pending.events.add("message_end");
|
|
137
|
+
|
|
138
|
+
console.log(
|
|
139
|
+
`[ConversationBridge] Assistant message captured: ${messageId.substring(0, 20)}...`,
|
|
140
|
+
);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
// Tool execution end - capture tool summaries
|
|
144
|
+
if (this.config.sendToolSummaries) {
|
|
145
|
+
this.pi.on("tool_execution_end", (event) => {
|
|
146
|
+
const summary = this.formatToolSummary(event);
|
|
147
|
+
if (summary) {
|
|
148
|
+
this.sendToolSummary(summary);
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Agent settled - finalize and send pending messages
|
|
154
|
+
this.pi.on("agent_settled", () => {
|
|
155
|
+
console.log("[ConversationBridge] Agent settled, finalizing messages...");
|
|
156
|
+
this.finalizePendingMessages();
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
console.log("[ConversationBridge] Lifecycle hooks registered");
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Extract text content from a message
|
|
164
|
+
*/
|
|
165
|
+
private extractMessageContent(
|
|
166
|
+
msg: { content?: unknown },
|
|
167
|
+
): string | null {
|
|
168
|
+
if (!msg.content) return null;
|
|
169
|
+
|
|
170
|
+
// Array of content parts
|
|
171
|
+
if (Array.isArray(msg.content)) {
|
|
172
|
+
const texts: string[] = [];
|
|
173
|
+
for (const part of msg.content) {
|
|
174
|
+
if (
|
|
175
|
+
part &&
|
|
176
|
+
typeof part === "object" &&
|
|
177
|
+
"type" in part &&
|
|
178
|
+
part.type === "text" &&
|
|
179
|
+
"text" in part
|
|
180
|
+
) {
|
|
181
|
+
texts.push(String(part.text));
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return texts.join("\n\n");
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Simple string
|
|
188
|
+
if (typeof msg.content === "string") {
|
|
189
|
+
return msg.content;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Format a tool execution summary
|
|
197
|
+
*/
|
|
198
|
+
private formatToolSummary(event: {
|
|
199
|
+
toolName: string;
|
|
200
|
+
result?: unknown;
|
|
201
|
+
isError: boolean;
|
|
202
|
+
}): string {
|
|
203
|
+
const status = event.isError ? "FAILED" : "DONE";
|
|
204
|
+
const resultPreview = this.truncate(
|
|
205
|
+
JSON.stringify(event.result ?? ""),
|
|
206
|
+
100,
|
|
207
|
+
);
|
|
208
|
+
return `[Tool] ${event.toolName}: ${status}${resultPreview ? ` - ${resultPreview}` : ""}`;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Send a tool summary to Telegram
|
|
213
|
+
*/
|
|
214
|
+
private async sendToolSummary(summary: string): Promise<void> {
|
|
215
|
+
if (!this.center.hasHealthyChannels()) return;
|
|
216
|
+
|
|
217
|
+
const payload: NotificationPayload = {
|
|
218
|
+
event: "CodexSessionStarted", // Reusing existing event
|
|
219
|
+
jobId: `tool-${Date.now()}`,
|
|
220
|
+
timestamp: new Date().toISOString(),
|
|
221
|
+
title: "Tool Executed",
|
|
222
|
+
message: summary,
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
try {
|
|
226
|
+
const results = await this.center.notify("CodexSessionStarted", {
|
|
227
|
+
jobId: payload.jobId,
|
|
228
|
+
requirement: payload.message,
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
for (const result of results) {
|
|
232
|
+
if (result.success) {
|
|
233
|
+
console.log(`[ConversationBridge] Tool summary sent`);
|
|
234
|
+
} else {
|
|
235
|
+
console.warn(`[ConversationBridge] Tool summary failed: ${result.error}`);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
} catch (err) {
|
|
239
|
+
console.error("[ConversationBridge] Failed to send tool summary:", err);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Finalize and send pending messages (called on agent_settled)
|
|
245
|
+
*/
|
|
246
|
+
private async finalizePendingMessages(): Promise<void> {
|
|
247
|
+
for (const [messageId, pending] of this.pendingMessages) {
|
|
248
|
+
if (this.deliveredMessageIds.has(messageId)) continue;
|
|
249
|
+
|
|
250
|
+
// Send the message
|
|
251
|
+
await this.sendAssistantResponse(pending.content, messageId);
|
|
252
|
+
this.deliveredMessageIds.add(messageId);
|
|
253
|
+
this.pendingMessages.delete(messageId);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Flush pending messages without sending (on compaction)
|
|
259
|
+
*/
|
|
260
|
+
private flushPendingMessages(): void {
|
|
261
|
+
console.log(
|
|
262
|
+
`[ConversationBridge] Flushing ${this.pendingMessages.size} pending messages`,
|
|
263
|
+
);
|
|
264
|
+
this.pendingMessages.clear();
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Send an assistant response to Telegram
|
|
269
|
+
*/
|
|
270
|
+
private async sendAssistantResponse(
|
|
271
|
+
content: string,
|
|
272
|
+
messageId: string,
|
|
273
|
+
): Promise<void> {
|
|
274
|
+
if (!this.center.hasHealthyChannels()) {
|
|
275
|
+
console.log("[ConversationBridge] No healthy channels, skipping");
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Chunk if too long
|
|
280
|
+
const chunks = this.chunkMessage(content);
|
|
281
|
+
|
|
282
|
+
for (let i = 0; i < chunks.length; i++) {
|
|
283
|
+
const chunk = chunks[i];
|
|
284
|
+
const chunkSuffix = chunks.length > 1 ? ` [${i + 1}/${chunks.length}]` : "";
|
|
285
|
+
|
|
286
|
+
const payload: NotificationPayload = {
|
|
287
|
+
event: "CodexSessionStarted", // Reusing event type
|
|
288
|
+
jobId: `msg-${messageId}`,
|
|
289
|
+
timestamp: new Date().toISOString(),
|
|
290
|
+
title: "Assistant Response",
|
|
291
|
+
message: chunk + chunkSuffix,
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
try {
|
|
295
|
+
const results = await this.center.notify("CodexSessionStarted", {
|
|
296
|
+
jobId: payload.jobId,
|
|
297
|
+
requirement: payload.message,
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
for (const result of results) {
|
|
301
|
+
if (result.success) {
|
|
302
|
+
console.log(
|
|
303
|
+
`[ConversationBridge] Response chunk ${i + 1}/${chunks.length} sent`,
|
|
304
|
+
);
|
|
305
|
+
} else {
|
|
306
|
+
console.warn(
|
|
307
|
+
`[ConversationBridge] Response chunk failed: ${result.error}`,
|
|
308
|
+
);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
} catch (err) {
|
|
312
|
+
console.error("[ConversationBridge] Failed to send response:", err);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Chunk a message into smaller parts
|
|
319
|
+
*/
|
|
320
|
+
private chunkMessage(message: string): string[] {
|
|
321
|
+
if (message.length <= this.config.maxMessageLength) {
|
|
322
|
+
return [message];
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
const chunks: string[] = [];
|
|
326
|
+
const sentences = message.match(/[^.!?]+[.!?]+/g) || [message];
|
|
327
|
+
let currentChunk = "";
|
|
328
|
+
|
|
329
|
+
for (const sentence of sentences) {
|
|
330
|
+
if ((currentChunk + sentence).length > this.config.maxMessageLength) {
|
|
331
|
+
if (currentChunk) {
|
|
332
|
+
chunks.push(currentChunk.trim());
|
|
333
|
+
}
|
|
334
|
+
currentChunk = sentence;
|
|
335
|
+
} else {
|
|
336
|
+
currentChunk += sentence;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
if (currentChunk) {
|
|
341
|
+
chunks.push(currentChunk.trim());
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
return chunks;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Truncate a string
|
|
349
|
+
*/
|
|
350
|
+
private truncate(str: string, maxLen: number): string {
|
|
351
|
+
if (str.length <= maxLen) return str;
|
|
352
|
+
return str.substring(0, maxLen - 3) + "...";
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Reset state for new session
|
|
357
|
+
*/
|
|
358
|
+
private reset(): void {
|
|
359
|
+
this.pendingMessages.clear();
|
|
360
|
+
this.deliveredMessageIds.clear();
|
|
361
|
+
this.lastMessageId = null;
|
|
362
|
+
this.sessionStartTime = Date.now();
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Stop the bridge (cleanup)
|
|
367
|
+
*/
|
|
368
|
+
stop(): void {
|
|
369
|
+
console.log("[ConversationBridge] Stopped");
|
|
370
|
+
// Cleanup will happen on shutdown
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* Create and register a conversation bridge
|
|
376
|
+
*/
|
|
377
|
+
export function createConversationBridge(
|
|
378
|
+
pi: ExtensionAPI,
|
|
379
|
+
center: NotificationCenter,
|
|
380
|
+
config?: Partial<ConversationBridgeConfig>,
|
|
381
|
+
): ConversationBridge {
|
|
382
|
+
const bridge = new ConversationBridge(pi, center, config);
|
|
383
|
+
bridge.register();
|
|
384
|
+
return bridge;
|
|
385
|
+
}
|
|
@@ -235,6 +235,19 @@ export class NotificationCenter {
|
|
|
235
235
|
return this.adapters.size > 0;
|
|
236
236
|
}
|
|
237
237
|
|
|
238
|
+
/**
|
|
239
|
+
* Check if any adapter is healthy (initialized and working)
|
|
240
|
+
* Unlike hasChannels(), this verifies actual health, not just config
|
|
241
|
+
*/
|
|
242
|
+
hasHealthyChannels(): boolean {
|
|
243
|
+
for (const [, adapter] of this.adapters) {
|
|
244
|
+
if (adapter.isHealthy()) {
|
|
245
|
+
return true;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
return false;
|
|
249
|
+
}
|
|
250
|
+
|
|
238
251
|
/**
|
|
239
252
|
* List all configured channels
|
|
240
253
|
*/
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-harness/provider-router",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.192",
|
|
4
4
|
"description": "Enhanced Provider Router for pi-harness-runtime — intelligent routing with capabilities, costs, and quotas",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/provider-router.js",
|