pi-harness-runtime 1.1.346 → 1.1.348
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 +71 -75
- 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/connection-runtime/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/contracts/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/durable-review/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-sink/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/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 +13 -0
- package/packages/notification/dist/notification-center.d.ts.map +1 -1
- package/packages/notification/dist/notification-center.js +91 -1
- package/packages/notification/dist/notification-center.js.map +1 -1
- package/packages/notification/dist/pending-question.d.ts +198 -0
- package/packages/notification/dist/pending-question.d.ts.map +1 -0
- package/packages/notification/dist/pending-question.js +410 -0
- package/packages/notification/dist/pending-question.js.map +1 -0
- package/packages/notification/package.json +1 -1
- package/packages/notification/src/index.ts +1 -0
- package/packages/notification/src/notification-center.ts +92 -1
- package/packages/notification/src/pending-question.ts +594 -0
- package/packages/notification/test/pending-question.test.ts +331 -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/permission-mode/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/relay/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/voice-api/package.json +1 -1
- package/packages/webmcp-tester/package.json +1 -1
- package/packages/wiki/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,410 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pending Question Controller
|
|
3
|
+
*
|
|
4
|
+
* Manages pending questions that require human response.
|
|
5
|
+
* Supports multiple channels (Telegram, LINE) and Jev evaluation.
|
|
6
|
+
*/
|
|
7
|
+
import { logWarn } from "./file-logger.js";
|
|
8
|
+
/**
|
|
9
|
+
* Pending Question Controller
|
|
10
|
+
*/
|
|
11
|
+
export class PendingQuestionController {
|
|
12
|
+
questions = new Map();
|
|
13
|
+
currentQuestion = null;
|
|
14
|
+
jevDecisionCallback;
|
|
15
|
+
userAnswerCallback;
|
|
16
|
+
config;
|
|
17
|
+
constructor(config) {
|
|
18
|
+
this.config = {
|
|
19
|
+
evaluationDelayMs: config?.evaluationDelayMs ?? 5 * 60 * 1000, // 5 minutes
|
|
20
|
+
jevEnabled: config?.jevEnabled ?? true,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Set callback for Jev decisions
|
|
25
|
+
*/
|
|
26
|
+
onJevDecision(callback) {
|
|
27
|
+
this.jevDecisionCallback = callback;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Set callback for user answers
|
|
31
|
+
*/
|
|
32
|
+
onUserAnswer(callback) {
|
|
33
|
+
this.userAnswerCallback = callback;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Generate unique request ID
|
|
37
|
+
*/
|
|
38
|
+
generateRequestId() {
|
|
39
|
+
return `q-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Detect if text contains a question requiring response
|
|
43
|
+
*/
|
|
44
|
+
detectQuestion(text, _options) {
|
|
45
|
+
// Find all question positions and extract each question sentence
|
|
46
|
+
const questionPositions = [];
|
|
47
|
+
let searchFrom = 0;
|
|
48
|
+
while (true) {
|
|
49
|
+
const questionIndex = text.indexOf('?', searchFrom);
|
|
50
|
+
if (questionIndex === -1)
|
|
51
|
+
break;
|
|
52
|
+
// Check if this question is inside quotes - if so, skip
|
|
53
|
+
const beforeQuestion = text.substring(0, questionIndex);
|
|
54
|
+
// Count unescaped quotes before and after
|
|
55
|
+
const openQuotes = (beforeQuestion.match(/"/g) || []).length;
|
|
56
|
+
const isInsideQuotes = openQuotes % 2 === 1;
|
|
57
|
+
if (isInsideQuotes) {
|
|
58
|
+
// This question is inside quotes - skip it
|
|
59
|
+
searchFrom = questionIndex + 1;
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
// Extract sentence for this question
|
|
63
|
+
let sentenceStart = searchFrom;
|
|
64
|
+
// Look backwards for sentence start
|
|
65
|
+
for (let i = questionIndex - 1; i >= searchFrom; i--) {
|
|
66
|
+
const char = text[i];
|
|
67
|
+
if (char === '.' || char === '!' || char === '\n') {
|
|
68
|
+
sentenceStart = i + 1;
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
const sentence = text.substring(sentenceStart, questionIndex + 1).trim();
|
|
73
|
+
questionPositions.push({ start: sentenceStart, end: questionIndex, sentence });
|
|
74
|
+
searchFrom = questionIndex + 1;
|
|
75
|
+
}
|
|
76
|
+
// Check each question for approval/clarification first (priority)
|
|
77
|
+
for (const q of questionPositions) {
|
|
78
|
+
// Skip quoted questions
|
|
79
|
+
if (this.isQuotedOrCode(text, q.sentence)) {
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
const isRoutine = this.isRoutineContinuation(q.sentence);
|
|
83
|
+
const kind = this.determineKind(q.sentence, isRoutine);
|
|
84
|
+
// Return first approval or clarification question found
|
|
85
|
+
if (kind === "approval" || kind === "clarification") {
|
|
86
|
+
return {
|
|
87
|
+
detected: true,
|
|
88
|
+
questionText: q.sentence,
|
|
89
|
+
kind,
|
|
90
|
+
isRoutine,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
// If no approval/clarification found, return first non-quoted routine question
|
|
95
|
+
for (const q of questionPositions) {
|
|
96
|
+
if (this.isQuotedOrCode(text, q.sentence)) {
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
const isRoutine = this.isRoutineContinuation(q.sentence);
|
|
100
|
+
return {
|
|
101
|
+
detected: true,
|
|
102
|
+
questionText: q.sentence,
|
|
103
|
+
kind: "routine",
|
|
104
|
+
isRoutine,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
// No question found - check for approval patterns without ?
|
|
108
|
+
const approvalResult = this.checkApprovalPatterns(text);
|
|
109
|
+
if (approvalResult)
|
|
110
|
+
return approvalResult;
|
|
111
|
+
return { detected: false };
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Check for approval patterns in text without question mark
|
|
115
|
+
* Only triggers if the text is short and looks like a question (no example: prefix)
|
|
116
|
+
*/
|
|
117
|
+
checkApprovalPatterns(text) {
|
|
118
|
+
// Don't trigger on example/quotation text or code
|
|
119
|
+
const lower = text.toLowerCase();
|
|
120
|
+
if (/\b(example|such as|like this|for instance)\b/.test(lower)) {
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
// Don't trigger on code blocks
|
|
124
|
+
if (/```/.test(text)) {
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
// Only trigger if text is reasonably short (likely a real question, not a paragraph)
|
|
128
|
+
if (text.length > 200) {
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
const approvalPatterns = [
|
|
132
|
+
/\bwant me to\b/i,
|
|
133
|
+
/\bwould you like me to\b/i,
|
|
134
|
+
/\bshall i\b/i,
|
|
135
|
+
/\bshould i add\b/i,
|
|
136
|
+
/\bshould i implement\b/i,
|
|
137
|
+
/\bshould we add\b/i,
|
|
138
|
+
/\bshould we implement\b/i,
|
|
139
|
+
/\bdo you want me to\b/i,
|
|
140
|
+
/\bcan i (start|proceed|continue)\b/i,
|
|
141
|
+
];
|
|
142
|
+
for (const pattern of approvalPatterns) {
|
|
143
|
+
if (pattern.test(text)) {
|
|
144
|
+
return {
|
|
145
|
+
detected: true,
|
|
146
|
+
questionText: text.trim(),
|
|
147
|
+
kind: "approval",
|
|
148
|
+
isRoutine: false,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Check if question is quoted or in code block
|
|
156
|
+
*/
|
|
157
|
+
isQuotedOrCode(fullText, question) {
|
|
158
|
+
// Normalize question for comparison - remove surrounding quotes if present
|
|
159
|
+
let normalizedQuestion = question.trim();
|
|
160
|
+
// Check if the question itself starts with a quote
|
|
161
|
+
const startsWithQuote = normalizedQuestion.startsWith('"') || normalizedQuestion.startsWith("'");
|
|
162
|
+
if (startsWithQuote) {
|
|
163
|
+
return true;
|
|
164
|
+
}
|
|
165
|
+
// Check if question appears inside code block
|
|
166
|
+
const codeBlockPattern = /```[\s\S]*?```/g;
|
|
167
|
+
const codeBlocks = fullText.match(codeBlockPattern) || [];
|
|
168
|
+
for (const block of codeBlocks) {
|
|
169
|
+
// Check if the normalized question is inside this code block
|
|
170
|
+
if (block.includes(normalizedQuestion)) {
|
|
171
|
+
return true;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
// Check if the question text appears inside quotes in the original full text
|
|
175
|
+
// This catches cases like: Here's an example: "Want me to implement this?"
|
|
176
|
+
// where the extracted sentence is "Here's an example: "Want me to implement this?"
|
|
177
|
+
// but we need to check if the CORE question is inside quotes
|
|
178
|
+
const questionWithoutQ = normalizedQuestion.replace(/\?$/, '');
|
|
179
|
+
// Look for the question text inside double quotes in the original text
|
|
180
|
+
// Pattern: find "...question...?" or "...question..."
|
|
181
|
+
const escapedQuestion = questionWithoutQ.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
182
|
+
const quotedQuestionPattern = new RegExp(`"[^"]*${escapedQuestion}[^"]*"`, 'i');
|
|
183
|
+
if (quotedQuestionPattern.test(fullText)) {
|
|
184
|
+
return true;
|
|
185
|
+
}
|
|
186
|
+
// Also check single quotes
|
|
187
|
+
const singleQuotedPattern = new RegExp(`'[^']*${escapedQuestion}[^']*'`, 'i');
|
|
188
|
+
if (singleQuotedPattern.test(fullText)) {
|
|
189
|
+
return true;
|
|
190
|
+
}
|
|
191
|
+
return false;
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Determine question kind
|
|
195
|
+
*/
|
|
196
|
+
determineKind(question, isRoutine) {
|
|
197
|
+
if (isRoutine)
|
|
198
|
+
return "routine";
|
|
199
|
+
const lower = question.toLowerCase();
|
|
200
|
+
// Clarification patterns (check first - more specific)
|
|
201
|
+
const clarifyPatterns = [
|
|
202
|
+
/\bdo you mean\b/i,
|
|
203
|
+
/\bwhich (one|option|approach)\b/i,
|
|
204
|
+
/\bshould i use\b/i,
|
|
205
|
+
/\bprefers?\b/i,
|
|
206
|
+
/\bwhich (one|option)\b/i,
|
|
207
|
+
];
|
|
208
|
+
for (const pattern of clarifyPatterns) {
|
|
209
|
+
if (pattern.test(lower)) {
|
|
210
|
+
return "clarification";
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
// Explicit approval patterns (want me to, would you like me to, etc.)
|
|
214
|
+
const approvalPatterns = [
|
|
215
|
+
/\bwant me to\b/i,
|
|
216
|
+
/\bwould you like me to\b/i,
|
|
217
|
+
/\bshall i\b/i,
|
|
218
|
+
/\bshould i add\b/i,
|
|
219
|
+
/\bshould i implement\b/i,
|
|
220
|
+
/\bshould we add\b/i,
|
|
221
|
+
/\bshould we implement\b/i,
|
|
222
|
+
/\bdo you want me to\b/i,
|
|
223
|
+
/\bcan i (start|proceed|add|implement)\b/i,
|
|
224
|
+
];
|
|
225
|
+
for (const pattern of approvalPatterns) {
|
|
226
|
+
if (pattern.test(lower)) {
|
|
227
|
+
return "approval";
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
return "routine";
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Check if it's a routine continuation question
|
|
234
|
+
*/
|
|
235
|
+
isRoutineContinuation(question) {
|
|
236
|
+
const lower = question.toLowerCase();
|
|
237
|
+
// Be specific - these patterns mean "continue with next task"
|
|
238
|
+
// Don't match approval patterns like "Would you like me to add X next?"
|
|
239
|
+
const routinePatterns = [
|
|
240
|
+
/\bshould i continue/i,
|
|
241
|
+
/\bshould we continue/i,
|
|
242
|
+
/\bcan i continue/i,
|
|
243
|
+
/\bcan we continue/i,
|
|
244
|
+
/\bmay i continue/i,
|
|
245
|
+
/\bshall i proceed/i,
|
|
246
|
+
/\bshould i proceed/i,
|
|
247
|
+
/^continue\?$/i,
|
|
248
|
+
/^proceed\?$/i,
|
|
249
|
+
/^next\?$/i,
|
|
250
|
+
/\bshall we (proceed|continue)/i,
|
|
251
|
+
/\bmay we (proceed|continue)/i,
|
|
252
|
+
/\bcan i proceed/i,
|
|
253
|
+
/\bshould i continue with/i,
|
|
254
|
+
/\bcan i proceed with/i,
|
|
255
|
+
];
|
|
256
|
+
for (const pattern of routinePatterns) {
|
|
257
|
+
if (pattern.test(lower)) {
|
|
258
|
+
return true;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
return false;
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Create a new pending question
|
|
265
|
+
*/
|
|
266
|
+
createQuestion(params) {
|
|
267
|
+
const requestId = this.generateRequestId();
|
|
268
|
+
const question = {
|
|
269
|
+
requestId,
|
|
270
|
+
sessionId: params.sessionId,
|
|
271
|
+
workspacePath: params.workspacePath,
|
|
272
|
+
jobId: params.jobId,
|
|
273
|
+
questionText: params.text,
|
|
274
|
+
kind: params.kind,
|
|
275
|
+
state: "pending",
|
|
276
|
+
createdAt: new Date(),
|
|
277
|
+
dueAt: new Date(Date.now() + this.config.evaluationDelayMs),
|
|
278
|
+
deliveryState: {},
|
|
279
|
+
userNotified: false,
|
|
280
|
+
revision: 1,
|
|
281
|
+
};
|
|
282
|
+
this.questions.set(requestId, question);
|
|
283
|
+
this.currentQuestion = question;
|
|
284
|
+
logWarn("[PendingQuestion]", `Created question ${requestId}: ${params.text.slice(0, 50)}...`);
|
|
285
|
+
return question;
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Get current pending question
|
|
289
|
+
*/
|
|
290
|
+
getCurrentQuestion() {
|
|
291
|
+
return this.currentQuestion;
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Check if there's a pending question
|
|
295
|
+
*/
|
|
296
|
+
hasPendingQuestion() {
|
|
297
|
+
return this.currentQuestion !== null &&
|
|
298
|
+
this.currentQuestion.state === "pending";
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Update delivery state for a channel
|
|
302
|
+
*/
|
|
303
|
+
updateDeliveryState(channel, delivered, error) {
|
|
304
|
+
if (!this.currentQuestion)
|
|
305
|
+
return;
|
|
306
|
+
this.currentQuestion.deliveryState[channel] = {
|
|
307
|
+
delivered,
|
|
308
|
+
deliveredAt: delivered ? new Date() : undefined,
|
|
309
|
+
error,
|
|
310
|
+
};
|
|
311
|
+
if (delivered) {
|
|
312
|
+
this.currentQuestion.userNotified = true;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* Resolve the current question
|
|
317
|
+
*/
|
|
318
|
+
resolve(params) {
|
|
319
|
+
if (!this.currentQuestion) {
|
|
320
|
+
logWarn("[PendingQuestion]", "No question to resolve");
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
this.currentQuestion.state = "resolved";
|
|
324
|
+
this.currentQuestion.resolution = params.resolution;
|
|
325
|
+
this.currentQuestion.resolvedBy = params.by;
|
|
326
|
+
this.currentQuestion.resolvedAt = new Date();
|
|
327
|
+
logWarn("[PendingQuestion]", `Resolved question ${this.currentQuestion.requestId}: ${params.resolution} by ${params.by}`);
|
|
328
|
+
// Notify callback
|
|
329
|
+
if (this.userAnswerCallback) {
|
|
330
|
+
this.userAnswerCallback(this.currentQuestion, params.resolution);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* Cancel the current question (e.g., on session switch)
|
|
335
|
+
*/
|
|
336
|
+
cancel(reason = "cancelled") {
|
|
337
|
+
if (!this.currentQuestion)
|
|
338
|
+
return;
|
|
339
|
+
this.currentQuestion.state = "cancelled";
|
|
340
|
+
logWarn("[PendingQuestion]", `Cancelled question ${this.currentQuestion.requestId}: ${reason}`);
|
|
341
|
+
this.currentQuestion = null;
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* Start Jev evaluation
|
|
345
|
+
*/
|
|
346
|
+
startEvaluation() {
|
|
347
|
+
if (!this.currentQuestion)
|
|
348
|
+
return;
|
|
349
|
+
if (this.currentQuestion.state !== "pending") {
|
|
350
|
+
logWarn("[PendingQuestion]", `Cannot start evaluation: state is ${this.currentQuestion.state}`);
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
this.currentQuestion.state = "evaluating";
|
|
354
|
+
logWarn("[PendingQuestion]", `Started evaluation for question ${this.currentQuestion.requestId}`);
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* Get pending question by request ID
|
|
358
|
+
*/
|
|
359
|
+
getQuestion(requestId) {
|
|
360
|
+
return this.questions.get(requestId);
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* Get all questions
|
|
364
|
+
*/
|
|
365
|
+
getAllQuestions() {
|
|
366
|
+
return Array.from(this.questions.values());
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* Clean up old resolved questions
|
|
370
|
+
*/
|
|
371
|
+
cleanup(maxAgeMs = 60 * 60 * 1000) {
|
|
372
|
+
const now = Date.now();
|
|
373
|
+
for (const [id, q] of this.questions.entries()) {
|
|
374
|
+
if (q.resolvedAt && (now - q.resolvedAt.getTime()) > maxAgeMs) {
|
|
375
|
+
this.questions.delete(id);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* Check if question is answered
|
|
381
|
+
*/
|
|
382
|
+
isResolved() {
|
|
383
|
+
return this.currentQuestion?.state === "resolved";
|
|
384
|
+
}
|
|
385
|
+
/**
|
|
386
|
+
* Get resolution
|
|
387
|
+
*/
|
|
388
|
+
getResolution() {
|
|
389
|
+
return this.currentQuestion?.resolution;
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* Check if should suppress completion
|
|
393
|
+
*/
|
|
394
|
+
shouldSuppressCompletion() {
|
|
395
|
+
return this.hasPendingQuestion();
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
/** Singleton instance */
|
|
399
|
+
let instance = null;
|
|
400
|
+
export function getPendingQuestionController() {
|
|
401
|
+
if (!instance) {
|
|
402
|
+
instance = new PendingQuestionController();
|
|
403
|
+
}
|
|
404
|
+
return instance;
|
|
405
|
+
}
|
|
406
|
+
export function createPendingQuestionController(config) {
|
|
407
|
+
instance = new PendingQuestionController(config);
|
|
408
|
+
return instance;
|
|
409
|
+
}
|
|
410
|
+
//# sourceMappingURL=pending-question.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pending-question.js","sourceRoot":"","sources":["../src/pending-question.ts"],"names":[],"mappings":"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"}
|
|
@@ -8,6 +8,7 @@ export * from "./notification-center.js";
|
|
|
8
8
|
export * from "./base-adapter.js";
|
|
9
9
|
export * from "./types.js";
|
|
10
10
|
export * from "./telegram-webhook-handler.js";
|
|
11
|
+
export * from "./pending-question.js";
|
|
11
12
|
export { requiresApproval, isHighPriority } from "./notification-policy.js";
|
|
12
13
|
export { LineAdapter } from "./adapters/line-adapter.js";
|
|
13
14
|
export { TelegramAdapter } from "./adapters/telegram-adapter.js";
|
|
@@ -10,6 +10,9 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import { logError, logWarn } from "./file-logger.js";
|
|
13
|
+
import { existsSync, mkdirSync } from "fs";
|
|
14
|
+
import { join } from "path";
|
|
15
|
+
import { DatabaseSync } from "node:sqlite";
|
|
13
16
|
|
|
14
17
|
import type {
|
|
15
18
|
InlineKeyboardButton,
|
|
@@ -45,9 +48,69 @@ export class NotificationCenter {
|
|
|
45
48
|
private adapters: Map<string, ChannelAdapter> = new Map();
|
|
46
49
|
private channelEnabled: Map<string, boolean> = new Map();
|
|
47
50
|
private redactPatterns: RegExp[];
|
|
51
|
+
private db: DatabaseSync | null = null;
|
|
48
52
|
/** Pending approval requests keyed by jobId */
|
|
49
53
|
private pendingApprovals: Map<string, PendingApproval> = new Map();
|
|
50
54
|
|
|
55
|
+
/**
|
|
56
|
+
* Get SQLite database path
|
|
57
|
+
*/
|
|
58
|
+
private static getDatabasePath(): string {
|
|
59
|
+
const home = process.env.HOME || process.env.USERPROFILE || "/home/frappe";
|
|
60
|
+
const dir = join(home, ".pi-harness-runtime");
|
|
61
|
+
if (!existsSync(dir)) {
|
|
62
|
+
mkdirSync(dir, { recursive: true });
|
|
63
|
+
}
|
|
64
|
+
return join(dir, "notification-settings.sqlite");
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Initialize SQLite database
|
|
69
|
+
*/
|
|
70
|
+
private initDatabase(): void {
|
|
71
|
+
try {
|
|
72
|
+
const dbPath = NotificationCenter.getDatabasePath();
|
|
73
|
+
this.db = new DatabaseSync(dbPath);
|
|
74
|
+
|
|
75
|
+
// Create tables
|
|
76
|
+
this.db.exec(`
|
|
77
|
+
CREATE TABLE IF NOT EXISTS channel_settings (
|
|
78
|
+
channel_id TEXT PRIMARY KEY,
|
|
79
|
+
enabled INTEGER NOT NULL DEFAULT 1,
|
|
80
|
+
updated_at TEXT DEFAULT CURRENT_TIMESTAMP
|
|
81
|
+
);
|
|
82
|
+
`);
|
|
83
|
+
|
|
84
|
+
// Load existing settings
|
|
85
|
+
const stmt = this.db.prepare("SELECT channel_id, enabled FROM channel_settings");
|
|
86
|
+
const rows = stmt.all() as Array<{ channel_id: string; enabled: number }>;
|
|
87
|
+
for (const row of rows) {
|
|
88
|
+
this.channelEnabled.set(row.channel_id, row.enabled === 1);
|
|
89
|
+
}
|
|
90
|
+
logWarn("[NotificationCenter]", `Loaded ${rows.length} channel settings from SQLite`);
|
|
91
|
+
} catch (e) {
|
|
92
|
+
logError("[NotificationCenter]", `Failed to init database: ${e}`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Save channel settings to SQLite
|
|
98
|
+
*/
|
|
99
|
+
private saveChannelSettings(): void {
|
|
100
|
+
if (!this.db) return;
|
|
101
|
+
try {
|
|
102
|
+
const stmt = this.db.prepare(`
|
|
103
|
+
INSERT OR REPLACE INTO channel_settings (channel_id, enabled, updated_at)
|
|
104
|
+
VALUES (?, ?, CURRENT_TIMESTAMP)
|
|
105
|
+
`);
|
|
106
|
+
for (const [id, enabled] of this.channelEnabled.entries()) {
|
|
107
|
+
stmt.run(id, enabled ? 1 : 0);
|
|
108
|
+
}
|
|
109
|
+
} catch (e) {
|
|
110
|
+
logError("[NotificationCenter]", `Failed to save settings: ${e}`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
51
114
|
/**
|
|
52
115
|
* Track a new pending approval request
|
|
53
116
|
*/
|
|
@@ -116,10 +179,17 @@ export class NotificationCenter {
|
|
|
116
179
|
this.redactPatterns =
|
|
117
180
|
config?.redactPatterns ?? this.getDefaultRedactPatterns();
|
|
118
181
|
|
|
182
|
+
// Initialize SQLite database and load settings
|
|
183
|
+
this.initDatabase();
|
|
184
|
+
|
|
119
185
|
if (config?.channels) {
|
|
120
186
|
for (const channelConfig of config.channels) {
|
|
121
187
|
if (channelConfig.enabled) {
|
|
122
188
|
this.registerAdapter(channelConfig);
|
|
189
|
+
// Set default enabled state (will be overridden by loaded settings)
|
|
190
|
+
if (!this.channelEnabled.has(channelConfig.id)) {
|
|
191
|
+
this.channelEnabled.set(channelConfig.id, true);
|
|
192
|
+
}
|
|
123
193
|
}
|
|
124
194
|
}
|
|
125
195
|
}
|
|
@@ -181,6 +251,7 @@ export class NotificationCenter {
|
|
|
181
251
|
*/
|
|
182
252
|
setChannelEnabled(channelId: string, enabled: boolean): void {
|
|
183
253
|
this.channelEnabled.set(channelId, enabled);
|
|
254
|
+
this.saveChannelSettings();
|
|
184
255
|
logWarn("[NotificationCenter]", `Channel ${channelId} ${enabled ? "enabled" : "disabled"}`);
|
|
185
256
|
}
|
|
186
257
|
|
|
@@ -424,7 +495,7 @@ export class NotificationCenter {
|
|
|
424
495
|
// Track this pending approval so callback can resolve it
|
|
425
496
|
this.trackPendingApproval(context.jobId, event, context);
|
|
426
497
|
|
|
427
|
-
// Send to each channel with buttons
|
|
498
|
+
// Send to each channel with buttons
|
|
428
499
|
const results: NotificationResult[] = [];
|
|
429
500
|
|
|
430
501
|
for (const [id, adapter] of this.adapters.entries()) {
|
|
@@ -440,6 +511,26 @@ export class NotificationCenter {
|
|
|
440
511
|
} catch (error) {
|
|
441
512
|
results.push({ success: false, channel: id, error: String(error) });
|
|
442
513
|
}
|
|
514
|
+
} else if (adapter instanceof LineAdapter) {
|
|
515
|
+
// LINE: Send approval request with text fallback directing user to answer via Telegram or Pi
|
|
516
|
+
try {
|
|
517
|
+
const linePayload: NotificationPayload = {
|
|
518
|
+
jobId: context.jobId,
|
|
519
|
+
timestamp: new Date().toISOString(),
|
|
520
|
+
event,
|
|
521
|
+
title: "Approval Required",
|
|
522
|
+
message: context.requirement || "A decision is needed.",
|
|
523
|
+
details: {
|
|
524
|
+
jobId: context.jobId,
|
|
525
|
+
...options?.additionalDetails,
|
|
526
|
+
},
|
|
527
|
+
};
|
|
528
|
+
const result = await adapter.send(linePayload);
|
|
529
|
+
results.push(result);
|
|
530
|
+
logWarn("[NotificationCenter]", `LINE approval sent to ${id}`);
|
|
531
|
+
} catch (error) {
|
|
532
|
+
results.push({ success: false, channel: id, error: String(error) });
|
|
533
|
+
}
|
|
443
534
|
}
|
|
444
535
|
}
|
|
445
536
|
|