pi-harness-runtime 1.1.232 → 1.1.233
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 -7
- 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/dist/continuation-controller.d.ts +14 -12
- package/packages/jev-judge/dist/continuation-controller.d.ts.map +1 -1
- package/packages/jev-judge/dist/continuation-controller.js +97 -65
- package/packages/jev-judge/dist/continuation-controller.js.map +1 -1
- package/packages/jev-judge/dist/todo-provider.d.ts +16 -1
- package/packages/jev-judge/dist/todo-provider.d.ts.map +1 -1
- package/packages/jev-judge/dist/todo-provider.js +22 -11
- package/packages/jev-judge/dist/todo-provider.js.map +1 -1
- package/packages/jev-judge/dist/typesafe-provider.d.ts.map +1 -1
- package/packages/jev-judge/dist/typesafe-provider.js +12 -3
- package/packages/jev-judge/dist/typesafe-provider.js.map +1 -1
- package/packages/jev-judge/package.json +1 -1
- package/packages/jev-judge/src/continuation-controller.ts +111 -77
- package/packages/jev-judge/src/todo-provider.ts +40 -21
- package/packages/jev-judge/src/typesafe-provider.ts +12 -3
- 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/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
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type { TodoItem } from "./todo-provider.js";
|
|
1
|
+
import type { TodoItem, TodoContext } from "./todo-provider.js";
|
|
3
2
|
import { createTodoProvider } from "./todo-provider.js";
|
|
4
3
|
|
|
5
4
|
/**
|
|
@@ -45,7 +44,8 @@ export interface ContinuationState {
|
|
|
45
44
|
continuationQueued: boolean;
|
|
46
45
|
userPaused: boolean;
|
|
47
46
|
lastSteerMessage: string | null;
|
|
48
|
-
|
|
47
|
+
steerAttemptsForCurrentItem: number;
|
|
48
|
+
lastItemContinued: number | null;
|
|
49
49
|
lastEvaluationResult: string | null;
|
|
50
50
|
}
|
|
51
51
|
|
|
@@ -65,7 +65,7 @@ export interface ContinuationResult {
|
|
|
65
65
|
* Evaluates todo state on agent_settled and determines next action.
|
|
66
66
|
*/
|
|
67
67
|
export class TodoContinuationController {
|
|
68
|
-
private
|
|
68
|
+
private ctx: TodoContext;
|
|
69
69
|
private config: Required<ContinuationConfig>;
|
|
70
70
|
private todoProvider: ReturnType<typeof createTodoProvider>;
|
|
71
71
|
|
|
@@ -79,13 +79,14 @@ export class TodoContinuationController {
|
|
|
79
79
|
continuationQueued: false,
|
|
80
80
|
userPaused: false,
|
|
81
81
|
lastSteerMessage: null,
|
|
82
|
-
|
|
82
|
+
steerAttemptsForCurrentItem: 0,
|
|
83
|
+
lastItemContinued: null,
|
|
83
84
|
lastEvaluationResult: null,
|
|
84
85
|
};
|
|
85
86
|
|
|
86
|
-
constructor(
|
|
87
|
-
this.
|
|
88
|
-
this.todoProvider = createTodoProvider(
|
|
87
|
+
constructor(ctx: TodoContext, config?: ContinuationConfig) {
|
|
88
|
+
this.ctx = ctx;
|
|
89
|
+
this.todoProvider = createTodoProvider(ctx);
|
|
89
90
|
this.config = {
|
|
90
91
|
jevFallbackMinWaitMs: 5 * 60 * 1000, // 5 minutes
|
|
91
92
|
routineThreshold: 0.7,
|
|
@@ -96,78 +97,99 @@ export class TodoContinuationController {
|
|
|
96
97
|
}
|
|
97
98
|
|
|
98
99
|
/**
|
|
99
|
-
*
|
|
100
|
+
* Check if continuation is allowed (not paused, session idle)
|
|
100
101
|
*/
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
debugLog("TodoContinuation", "Session
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
// User input - reset pause state
|
|
118
|
-
this.pi.on("message_start", (event) => {
|
|
119
|
-
const msg = event.message as { role?: string };
|
|
120
|
-
if (msg?.role === "user") {
|
|
121
|
-
this.state.userPaused = false;
|
|
122
|
-
this.state.lastSteerMessage = null;
|
|
123
|
-
}
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
// Session shutdown - cleanup
|
|
127
|
-
this.pi.on("session_shutdown", () => {
|
|
128
|
-
this.cleanup();
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
// Tool call - track mutations that might invalidate state
|
|
132
|
-
this.pi.on("tool_call", (event) => {
|
|
133
|
-
// Increment generation on edits
|
|
134
|
-
if (["write", "edit", "bash"].includes(event.toolName)) {
|
|
135
|
-
this.state.generation++;
|
|
136
|
-
this.state.continuationQueued = false;
|
|
137
|
-
}
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
debugLog("TodoContinuation", "Lifecycle hooks registered");
|
|
102
|
+
private canContinue(): boolean {
|
|
103
|
+
if (this.state.userPaused) {
|
|
104
|
+
debugLog("TodoContinuation", "User paused, not continuing");
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
if (this.ctx.isIdle?.() === false) {
|
|
108
|
+
debugLog("TodoContinuation", "Session not idle, not continuing");
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
if (this.ctx.hasPendingMessages?.()) {
|
|
112
|
+
debugLog("TodoContinuation", "Pending messages, not continuing");
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
return true;
|
|
141
116
|
}
|
|
142
117
|
|
|
143
118
|
/**
|
|
144
119
|
* Public method to evaluate and execute continuation
|
|
145
120
|
* Called by external handlers (e.g., agent_settled)
|
|
121
|
+
* Checks blockers and pause BEFORE selecting a task
|
|
146
122
|
*/
|
|
147
123
|
async decide(): Promise<ContinuationResult> {
|
|
124
|
+
// 1. Check if continuation is allowed
|
|
125
|
+
if (!this.canContinue()) {
|
|
126
|
+
return { action: "stop", reason: "paused or session not idle" };
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// 2. Check provider availability
|
|
148
130
|
const availability = await this.todoProvider.availability();
|
|
149
131
|
if (!availability.available) {
|
|
150
132
|
debugLog("TodoContinuation", "Provider unavailable:", availability.error);
|
|
151
|
-
return { action: "stop", reason: availability.error };
|
|
133
|
+
return { action: "stop", reason: availability.error ?? "provider unavailable" };
|
|
152
134
|
}
|
|
153
135
|
|
|
154
|
-
|
|
155
|
-
|
|
136
|
+
// 3. Get todo state (throws on branch read failure)
|
|
137
|
+
let inProgress: TodoItem[];
|
|
138
|
+
let pending: TodoItem[];
|
|
139
|
+
let blocked: TodoItem[];
|
|
140
|
+
try {
|
|
141
|
+
inProgress = await this.todoProvider.getInProgress();
|
|
142
|
+
pending = await this.todoProvider.getPending();
|
|
143
|
+
blocked = await this.todoProvider.getBlocked();
|
|
144
|
+
} catch (err) {
|
|
145
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
146
|
+
debugLog("TodoContinuation", "Failed to read todos:", msg);
|
|
147
|
+
return { action: "stop", reason: `todo read failed: ${msg}` };
|
|
148
|
+
}
|
|
156
149
|
|
|
157
|
-
|
|
158
|
-
|
|
150
|
+
// 4. Check for blocked items FIRST (before selecting any task)
|
|
151
|
+
if (blocked.length > 0) {
|
|
152
|
+
debugLog("TodoContinuation", "Tasks blocked:", blocked.length);
|
|
153
|
+
return { action: "blocked", question: `${blocked.length} task(s) blocked` };
|
|
159
154
|
}
|
|
160
155
|
|
|
161
|
-
|
|
162
|
-
|
|
156
|
+
// 5. Continue in-progress work if any
|
|
157
|
+
if (inProgress.length > 0) {
|
|
158
|
+
const item = inProgress[0];
|
|
159
|
+
|
|
160
|
+
// Reset counter if switching to a different task
|
|
161
|
+
if (this.state.lastItemContinued !== item.id) {
|
|
162
|
+
this.state.steerAttemptsForCurrentItem = 0;
|
|
163
|
+
this.state.lastItemContinued = item.id;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Check per-task attempt limit
|
|
167
|
+
if (this.state.steerAttemptsForCurrentItem >= this.config.maxSteerAttempts) {
|
|
168
|
+
return { action: "stop", reason: `max attempts (${this.config.maxSteerAttempts}) reached for task ${item.id}` };
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return { action: "continue", nextItemId: item.id };
|
|
163
172
|
}
|
|
164
173
|
|
|
165
|
-
//
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
174
|
+
// 6. Continue pending work if any
|
|
175
|
+
if (pending.length > 0) {
|
|
176
|
+
const item = pending[0];
|
|
177
|
+
|
|
178
|
+
// Reset counter if switching to a different task
|
|
179
|
+
if (this.state.lastItemContinued !== item.id) {
|
|
180
|
+
this.state.steerAttemptsForCurrentItem = 0;
|
|
181
|
+
this.state.lastItemContinued = item.id;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Check per-task attempt limit
|
|
185
|
+
if (this.state.steerAttemptsForCurrentItem >= this.config.maxSteerAttempts) {
|
|
186
|
+
return { action: "stop", reason: `max attempts (${this.config.maxSteerAttempts}) reached for task ${item.id}` };
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return { action: "continue", nextItemId: item.id };
|
|
169
190
|
}
|
|
170
191
|
|
|
192
|
+
// 7. No work remaining
|
|
171
193
|
return { action: "stop", reason: "No pending tasks" };
|
|
172
194
|
}
|
|
173
195
|
|
|
@@ -239,13 +261,19 @@ export class TodoContinuationController {
|
|
|
239
261
|
/**
|
|
240
262
|
* Continue a specific todo item
|
|
241
263
|
*/
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
264
|
+
async continueItem(item: TodoItem): Promise<void> {
|
|
265
|
+
// Increment attempts for this specific item
|
|
266
|
+
if (this.state.lastItemContinued !== item.id) {
|
|
267
|
+
this.state.steerAttemptsForCurrentItem = 0;
|
|
268
|
+
this.state.lastItemContinued = item.id;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (this.state.steerAttemptsForCurrentItem >= this.config.maxSteerAttempts) {
|
|
272
|
+
debugLog("TodoContinuation", "Max steer attempts reached for item", item.id);
|
|
245
273
|
return;
|
|
246
274
|
}
|
|
247
275
|
|
|
248
|
-
this.state.
|
|
276
|
+
this.state.steerAttemptsForCurrentItem++;
|
|
249
277
|
this.state.continuationQueued = true;
|
|
250
278
|
|
|
251
279
|
// Build continuation message
|
|
@@ -258,7 +286,7 @@ export class TodoContinuationController {
|
|
|
258
286
|
|
|
259
287
|
// Send continuation message via Pi API
|
|
260
288
|
try {
|
|
261
|
-
await
|
|
289
|
+
await this.ctx.sendUserMessage?.(message, { deliverAs: "steer" });
|
|
262
290
|
} catch (err) {
|
|
263
291
|
debugLog("TodoContinuation", "Failed to send continuation:", String(err));
|
|
264
292
|
this.state.continuationQueued = false;
|
|
@@ -269,12 +297,18 @@ export class TodoContinuationController {
|
|
|
269
297
|
* Steer to continue a todo item
|
|
270
298
|
*/
|
|
271
299
|
async steerToContinue(itemId: number): Promise<void> {
|
|
272
|
-
|
|
273
|
-
|
|
300
|
+
// Increment attempts for this specific item
|
|
301
|
+
if (this.state.lastItemContinued !== itemId) {
|
|
302
|
+
this.state.steerAttemptsForCurrentItem = 0;
|
|
303
|
+
this.state.lastItemContinued = itemId;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if (this.state.steerAttemptsForCurrentItem >= this.config.maxSteerAttempts) {
|
|
307
|
+
debugLog("TodoContinuation", "Max steer attempts reached for item", itemId);
|
|
274
308
|
return;
|
|
275
309
|
}
|
|
276
310
|
|
|
277
|
-
this.state.
|
|
311
|
+
this.state.steerAttemptsForCurrentItem++;
|
|
278
312
|
this.state.continuationQueued = true;
|
|
279
313
|
|
|
280
314
|
const snapshot = await this.todoProvider.getSnapshot();
|
|
@@ -290,7 +324,7 @@ export class TodoContinuationController {
|
|
|
290
324
|
|
|
291
325
|
// Send continuation message via Pi API
|
|
292
326
|
try {
|
|
293
|
-
await
|
|
327
|
+
await this.ctx.sendUserMessage?.(message, { deliverAs: "steer" });
|
|
294
328
|
} catch (err) {
|
|
295
329
|
debugLog("TodoContinuation", "Failed to send steer:", String(err));
|
|
296
330
|
this.state.continuationQueued = false;
|
|
@@ -318,7 +352,7 @@ export class TodoContinuationController {
|
|
|
318
352
|
/**
|
|
319
353
|
* Reset state for new session
|
|
320
354
|
*/
|
|
321
|
-
|
|
355
|
+
_reset(): void {
|
|
322
356
|
this.state = {
|
|
323
357
|
sessionId: `session-${Date.now()}`,
|
|
324
358
|
generation: 0,
|
|
@@ -329,7 +363,8 @@ export class TodoContinuationController {
|
|
|
329
363
|
continuationQueued: false,
|
|
330
364
|
userPaused: false,
|
|
331
365
|
lastSteerMessage: null,
|
|
332
|
-
|
|
366
|
+
steerAttemptsForCurrentItem: 0,
|
|
367
|
+
lastItemContinued: null,
|
|
333
368
|
lastEvaluationResult: null,
|
|
334
369
|
};
|
|
335
370
|
}
|
|
@@ -337,7 +372,7 @@ export class TodoContinuationController {
|
|
|
337
372
|
/**
|
|
338
373
|
* Cleanup on shutdown
|
|
339
374
|
*/
|
|
340
|
-
|
|
375
|
+
_cleanup(): void {
|
|
341
376
|
debugLog("TodoContinuation", "Cleaning up");
|
|
342
377
|
this.state.continuationQueued = false;
|
|
343
378
|
this.state.generation++;
|
|
@@ -356,13 +391,12 @@ export class TodoContinuationController {
|
|
|
356
391
|
}
|
|
357
392
|
|
|
358
393
|
/**
|
|
359
|
-
* Create
|
|
394
|
+
* Create a todo continuation controller
|
|
395
|
+
* Note: Caller is responsible for registering event handlers with the proper context
|
|
360
396
|
*/
|
|
361
397
|
export function createTodoContinuation(
|
|
362
|
-
|
|
398
|
+
ctx: TodoContext,
|
|
363
399
|
config?: ContinuationConfig
|
|
364
400
|
): TodoContinuationController {
|
|
365
|
-
|
|
366
|
-
controller.register();
|
|
367
|
-
return controller;
|
|
401
|
+
return new TodoContinuationController(ctx, config);
|
|
368
402
|
}
|
|
@@ -7,6 +7,20 @@
|
|
|
7
7
|
|
|
8
8
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
9
9
|
|
|
10
|
+
/**
|
|
11
|
+
* Minimal context interface for todo provider
|
|
12
|
+
* Contains only the properties needed to read session branch and send messages
|
|
13
|
+
*/
|
|
14
|
+
export interface TodoContext {
|
|
15
|
+
sessionManager: {
|
|
16
|
+
getSessionId(): string;
|
|
17
|
+
getBranch(): Iterable<unknown>;
|
|
18
|
+
};
|
|
19
|
+
isIdle?: () => boolean;
|
|
20
|
+
hasPendingMessages?: () => boolean;
|
|
21
|
+
sendUserMessage?: (content: string, opts?: { deliverAs?: string }) => Promise<void>;
|
|
22
|
+
}
|
|
23
|
+
|
|
10
24
|
/**
|
|
11
25
|
* Todo item status
|
|
12
26
|
*/
|
|
@@ -175,22 +189,15 @@ export class MockTodoProvider implements TodoProvider {
|
|
|
175
189
|
}
|
|
176
190
|
}
|
|
177
191
|
|
|
178
|
-
|
|
179
|
-
* Extended ExtensionAPI type with sessionManager (not in base type but available at runtime)
|
|
180
|
-
*/
|
|
181
|
-
interface PiExtensionAPI extends ExtensionAPI {
|
|
182
|
-
sessionManager: {
|
|
183
|
-
getSessionId(): string;
|
|
184
|
-
getBranch(): Iterable<unknown>;
|
|
185
|
-
};
|
|
186
|
-
}
|
|
192
|
+
|
|
187
193
|
|
|
188
194
|
/**
|
|
189
195
|
* Create a todo provider that reads from rpiv-todo state via session branch
|
|
190
196
|
*/
|
|
191
|
-
export function createTodoProvider(
|
|
192
|
-
|
|
193
|
-
|
|
197
|
+
export function createTodoProvider(ctx?: TodoContext | ExtensionAPI): TodoProvider {
|
|
198
|
+
// Accept either TodoContext or ExtensionAPI with sessionManager
|
|
199
|
+
if (ctx && "sessionManager" in ctx) {
|
|
200
|
+
return new PiTodoProvider(ctx as TodoContext);
|
|
194
201
|
}
|
|
195
202
|
return new PiUnavailableTodoProvider();
|
|
196
203
|
}
|
|
@@ -200,15 +207,16 @@ export function createTodoProvider(pi?: ExtensionAPI): TodoProvider {
|
|
|
200
207
|
* Replicates rpiv-todo's replayFromBranch logic to access todo state
|
|
201
208
|
*/
|
|
202
209
|
class PiTodoProvider implements TodoProvider {
|
|
203
|
-
private
|
|
210
|
+
private ctx: TodoContext;
|
|
204
211
|
private _revision = 0;
|
|
212
|
+
private _lastError: string | null = null;
|
|
205
213
|
|
|
206
|
-
constructor(
|
|
207
|
-
this.
|
|
214
|
+
constructor(ctx: TodoContext) {
|
|
215
|
+
this.ctx = ctx;
|
|
208
216
|
}
|
|
209
217
|
|
|
210
218
|
async availability(): Promise<TodoAvailability> {
|
|
211
|
-
if (!this.
|
|
219
|
+
if (!this.ctx.sessionManager) {
|
|
212
220
|
return {
|
|
213
221
|
available: false,
|
|
214
222
|
error: "Session manager not available",
|
|
@@ -229,13 +237,15 @@ class PiTodoProvider implements TodoProvider {
|
|
|
229
237
|
|
|
230
238
|
/**
|
|
231
239
|
* Get todo state from session branch (replicates rpiv-todo's replayFromBranch)
|
|
240
|
+
* Returns null if branch read fails (don't silently return empty state)
|
|
232
241
|
*/
|
|
233
|
-
private getTodoState(): { tasks: TodoItem[]; nextId: number } {
|
|
242
|
+
private getTodoState(): { tasks: TodoItem[]; nextId: number } | null {
|
|
234
243
|
let fromEntries: { tasks: TodoItem[]; nextId: number } | undefined;
|
|
235
244
|
let fromToolResults: { tasks: TodoItem[]; nextId: number } | undefined;
|
|
236
245
|
|
|
237
246
|
try {
|
|
238
|
-
|
|
247
|
+
const branch = this.ctx.sessionManager.getBranch();
|
|
248
|
+
for (const entry of branch) {
|
|
239
249
|
const e = entry as {
|
|
240
250
|
type?: string;
|
|
241
251
|
customType?: string;
|
|
@@ -259,19 +269,28 @@ class PiTodoProvider implements TodoProvider {
|
|
|
259
269
|
fromToolResults = { tasks: (msg.details as { tasks: TodoItem[] }).tasks.map(t => ({ ...t })), nextId: (msg.details as { nextId: number }).nextId };
|
|
260
270
|
}
|
|
261
271
|
} catch (err) {
|
|
262
|
-
//
|
|
272
|
+
// Return null on error - don't silently return empty state
|
|
273
|
+
this._lastError = err instanceof Error ? err.message : String(err);
|
|
274
|
+
return null;
|
|
263
275
|
}
|
|
264
276
|
|
|
265
277
|
// Prefer custom entries (more authoritative), fall back to tool results
|
|
266
|
-
|
|
278
|
+
this._lastError = null;
|
|
279
|
+
return fromEntries ?? fromToolResults ?? null;
|
|
267
280
|
}
|
|
268
281
|
|
|
269
282
|
async getSnapshot(): Promise<TodoSnapshot> {
|
|
270
283
|
this._revision++;
|
|
271
284
|
const state = this.getTodoState();
|
|
285
|
+
|
|
286
|
+
// If branch read failed, throw to indicate unavailable state
|
|
287
|
+
if (state === null) {
|
|
288
|
+
throw new Error(`Todo state unavailable: ${this._lastError ?? "branch read failed"}`);
|
|
289
|
+
}
|
|
290
|
+
|
|
272
291
|
return {
|
|
273
292
|
items: state.tasks,
|
|
274
|
-
sessionId: this.
|
|
293
|
+
sessionId: this.ctx.sessionManager.getSessionId() ?? "unknown",
|
|
275
294
|
revision: this._revision,
|
|
276
295
|
createdAt: new Date(),
|
|
277
296
|
};
|
|
@@ -258,12 +258,21 @@ export class TypeSafeJudge {
|
|
|
258
258
|
|
|
259
259
|
switch (answer.type) {
|
|
260
260
|
case "noul":
|
|
261
|
-
// FIX:
|
|
261
|
+
// FIX: Validate noul value is present and in range [0, 1]
|
|
262
|
+
if (answer.noul === undefined || answer.noul === null) {
|
|
263
|
+
throw new Error(`TypeSafe API: missing noul value for question "${questionId}"`);
|
|
264
|
+
}
|
|
265
|
+
if (typeof answer.noul !== "number" || isNaN(answer.noul)) {
|
|
266
|
+
throw new Error(`TypeSafe API: invalid noul value for question "${questionId}": ${JSON.stringify(answer.noul)}`);
|
|
267
|
+
}
|
|
268
|
+
if (answer.noul < 0 || answer.noul > 1) {
|
|
269
|
+
throw new Error(`TypeSafe API: noul value out of range [0,1] for question "${questionId}": ${answer.noul}`);
|
|
270
|
+
}
|
|
262
271
|
response = {
|
|
263
272
|
type: "noul",
|
|
264
|
-
noul: answer.noul
|
|
273
|
+
noul: answer.noul,
|
|
265
274
|
};
|
|
266
|
-
probability = answer.noul
|
|
275
|
+
probability = answer.noul;
|
|
267
276
|
break;
|
|
268
277
|
|
|
269
278
|
case "choice":
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-harness/provider-router",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.233",
|
|
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",
|