pi-harness-runtime 1.1.236 → 1.1.238
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 +68 -5
- 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 +20 -0
- package/packages/jev-judge/dist/continuation-controller.d.ts.map +1 -1
- package/packages/jev-judge/dist/continuation-controller.js +57 -6
- package/packages/jev-judge/dist/continuation-controller.js.map +1 -1
- package/packages/jev-judge/dist/index.d.ts +5 -3
- package/packages/jev-judge/dist/index.d.ts.map +1 -1
- package/packages/jev-judge/dist/index.js +3 -1
- package/packages/jev-judge/dist/index.js.map +1 -1
- package/packages/jev-judge/dist/task-intake.d.ts +91 -0
- package/packages/jev-judge/dist/task-intake.d.ts.map +1 -0
- package/packages/jev-judge/dist/task-intake.js +232 -0
- package/packages/jev-judge/dist/task-intake.js.map +1 -0
- package/packages/jev-judge/package.json +1 -1
- package/packages/jev-judge/src/continuation-controller.ts +64 -6
- package/packages/jev-judge/src/index.ts +7 -4
- package/packages/jev-judge/src/task-intake.ts +302 -0
- 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
|
@@ -45,6 +45,7 @@ export interface ContinuationState {
|
|
|
45
45
|
lastMessageId: string | null;
|
|
46
46
|
continuationQueued: boolean;
|
|
47
47
|
userPaused: boolean;
|
|
48
|
+
explicitPause: boolean; // True when user explicitly paused
|
|
48
49
|
lastSteerMessage: string | null;
|
|
49
50
|
steerAttemptsForCurrentItem: number;
|
|
50
51
|
lastItemContinued: number | null;
|
|
@@ -80,12 +81,15 @@ export class TodoContinuationController {
|
|
|
80
81
|
lastMessageId: null,
|
|
81
82
|
continuationQueued: false,
|
|
82
83
|
userPaused: false,
|
|
84
|
+
explicitPause: false,
|
|
83
85
|
lastSteerMessage: null,
|
|
84
86
|
steerAttemptsForCurrentItem: 0,
|
|
85
87
|
lastItemContinued: null,
|
|
86
88
|
lastEvaluationResult: null,
|
|
87
89
|
};
|
|
88
90
|
|
|
91
|
+
private _inFlight = false; // Guard against concurrent dispatches
|
|
92
|
+
|
|
89
93
|
constructor(ctx: TodoContext, config?: ContinuationConfig) {
|
|
90
94
|
this.ctx = ctx;
|
|
91
95
|
this.todoProvider = createTodoProvider(ctx);
|
|
@@ -102,8 +106,9 @@ export class TodoContinuationController {
|
|
|
102
106
|
* Check if continuation is allowed (not paused, session idle)
|
|
103
107
|
*/
|
|
104
108
|
private canContinue(): boolean {
|
|
105
|
-
|
|
106
|
-
|
|
109
|
+
// Only blocked by explicit pause, not by temporary user input pause
|
|
110
|
+
if (this.state.explicitPause) {
|
|
111
|
+
debugLog("TodoContinuation", "Explicitly paused, not continuing");
|
|
107
112
|
return false;
|
|
108
113
|
}
|
|
109
114
|
if (this.ctx.isIdle?.() === false) {
|
|
@@ -117,6 +122,45 @@ export class TodoContinuationController {
|
|
|
117
122
|
return true;
|
|
118
123
|
}
|
|
119
124
|
|
|
125
|
+
/**
|
|
126
|
+
* User explicitly paused - only this clears on explicit resume
|
|
127
|
+
*/
|
|
128
|
+
explicitPause(): void {
|
|
129
|
+
debugLog("TodoContinuation", "Explicit pause requested");
|
|
130
|
+
this.state.explicitPause = true;
|
|
131
|
+
this.state.userPaused = true;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Resume from explicit pause
|
|
136
|
+
*/
|
|
137
|
+
resume(): void {
|
|
138
|
+
if (this.state.explicitPause) {
|
|
139
|
+
debugLog("TodoContinuation", "Resuming from explicit pause");
|
|
140
|
+
this.state.explicitPause = false;
|
|
141
|
+
this.state.userPaused = false;
|
|
142
|
+
this.clearQueue();
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Temporary pause for user input processing
|
|
148
|
+
* Does not require explicit resume
|
|
149
|
+
*/
|
|
150
|
+
tempPause(): void {
|
|
151
|
+
debugLog("TodoContinuation", "Temporary pause for user input");
|
|
152
|
+
this.state.userPaused = true;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Clear continuation queue (called when work completes or starts)
|
|
157
|
+
*/
|
|
158
|
+
clearQueue(): void {
|
|
159
|
+
this.state.continuationQueued = false;
|
|
160
|
+
this._inFlight = false;
|
|
161
|
+
debugLog("TodoContinuation", "Continuation queue cleared");
|
|
162
|
+
}
|
|
163
|
+
|
|
120
164
|
/**
|
|
121
165
|
* Public method to evaluate and execute continuation
|
|
122
166
|
* Called by external handlers (e.g., agent_settled)
|
|
@@ -244,11 +288,18 @@ export class TodoContinuationController {
|
|
|
244
288
|
|
|
245
289
|
/**
|
|
246
290
|
* Guarded dispatch - checks all conditions before sending
|
|
291
|
+
* Uses in-flight guard to prevent race conditions
|
|
247
292
|
*/
|
|
248
293
|
private async guardedDispatch(itemId: number, message: string): Promise<boolean> {
|
|
249
|
-
//
|
|
250
|
-
if (this.
|
|
251
|
-
debugLog("TodoContinuation", "Guarded dispatch:
|
|
294
|
+
// 0. Acquire in-flight guard to prevent concurrent dispatches
|
|
295
|
+
if (this._inFlight) {
|
|
296
|
+
debugLog("TodoContinuation", "Guarded dispatch: already in-flight, skipping");
|
|
297
|
+
return false;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// 1. Check if paused by explicit user pause
|
|
301
|
+
if (this.state.explicitPause) {
|
|
302
|
+
debugLog("TodoContinuation", "Guarded dispatch: explicitly paused, skipping");
|
|
252
303
|
return false;
|
|
253
304
|
}
|
|
254
305
|
|
|
@@ -264,17 +315,20 @@ export class TodoContinuationController {
|
|
|
264
315
|
return false;
|
|
265
316
|
}
|
|
266
317
|
|
|
267
|
-
// 4. Verify item still needs work (not completed)
|
|
318
|
+
// 4. Verify item still needs work (not completed) - acquire guard BEFORE await
|
|
319
|
+
this._inFlight = true;
|
|
268
320
|
try {
|
|
269
321
|
const snapshot = await this.todoProvider.getSnapshot();
|
|
270
322
|
const item = snapshot.items.find((i) => i.id === itemId);
|
|
271
323
|
if (item?.status === "completed") {
|
|
272
324
|
debugLog("TodoContinuation", "Guarded dispatch: item completed, skipping");
|
|
325
|
+
this._inFlight = false;
|
|
273
326
|
return false;
|
|
274
327
|
}
|
|
275
328
|
} catch {
|
|
276
329
|
// If we can't read todos, skip dispatch
|
|
277
330
|
debugLog("TodoContinuation", "Guarded dispatch: failed to read todos, skipping");
|
|
331
|
+
this._inFlight = false;
|
|
278
332
|
return false;
|
|
279
333
|
}
|
|
280
334
|
|
|
@@ -284,10 +338,12 @@ export class TodoContinuationController {
|
|
|
284
338
|
|
|
285
339
|
try {
|
|
286
340
|
await this.config.dispatch(message);
|
|
341
|
+
// Queue stays set while agent processes - cleared on agent_settled
|
|
287
342
|
return true;
|
|
288
343
|
} catch (err) {
|
|
289
344
|
debugLog("TodoContinuation", "Guarded dispatch failed:", String(err));
|
|
290
345
|
this.state.continuationQueued = false;
|
|
346
|
+
this._inFlight = false;
|
|
291
347
|
return false;
|
|
292
348
|
}
|
|
293
349
|
}
|
|
@@ -350,11 +406,13 @@ export class TodoContinuationController {
|
|
|
350
406
|
lastMessageId: null,
|
|
351
407
|
continuationQueued: false,
|
|
352
408
|
userPaused: false,
|
|
409
|
+
explicitPause: false,
|
|
353
410
|
lastSteerMessage: null,
|
|
354
411
|
steerAttemptsForCurrentItem: 0,
|
|
355
412
|
lastItemContinued: null,
|
|
356
413
|
lastEvaluationResult: null,
|
|
357
414
|
};
|
|
415
|
+
this._inFlight = false;
|
|
358
416
|
}
|
|
359
417
|
|
|
360
418
|
/**
|
|
@@ -39,7 +39,6 @@ import type {
|
|
|
39
39
|
JevEvaluationResult,
|
|
40
40
|
JevNoulResponse,
|
|
41
41
|
JevChoiceResponse,
|
|
42
|
-
JevScoreResponse,
|
|
43
42
|
ThresholdDecision,
|
|
44
43
|
} from "./types.js";
|
|
45
44
|
|
|
@@ -427,7 +426,7 @@ export type { TaskState, AutoContinueDecision, AutoContinueConfig } from "./auto
|
|
|
427
426
|
|
|
428
427
|
// Export WrapUpJudge
|
|
429
428
|
export { checkWrapUp, getTodoSummary } from "./wrap-up-judge.js";
|
|
430
|
-
export type {
|
|
429
|
+
export type { WrapUpResult } from "./wrap-up-judge.js";
|
|
431
430
|
|
|
432
431
|
// Export EnvironmentJudge
|
|
433
432
|
export { EnvironmentJudge } from "./environment-judge.js";
|
|
@@ -442,5 +441,9 @@ export { TodoContinuationController, createTodoContinuation } from "./continuati
|
|
|
442
441
|
export type { ContinuationConfig, ContinuationState, ContinuationResult } from "./continuation-controller.js";
|
|
443
442
|
|
|
444
443
|
// Export TodoProvider
|
|
445
|
-
export { createTodoProvider
|
|
446
|
-
export type { TodoProvider,
|
|
444
|
+
export { createTodoProvider } from "./todo-provider.js";
|
|
445
|
+
export type { TodoProvider, TodoAvailability, TodoItem, TodoStatus, TodoSnapshot, TodoContext } from "./todo-provider.js";
|
|
446
|
+
|
|
447
|
+
// Export Task Intake
|
|
448
|
+
export { TaskIntakeController, createTaskIntake } from "./task-intake.js";
|
|
449
|
+
export type { TaskIntakeConfig, TaskIntakeResult } from "./task-intake.js";
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Task Intake - Automatic todo capture from user requests
|
|
3
|
+
*
|
|
4
|
+
* Uses Jev Yes/No judgments to determine:
|
|
5
|
+
* - Whether a user request should be tracked as work
|
|
6
|
+
* - Whether it duplicates existing work
|
|
7
|
+
* - Whether it modifies an existing requirement
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { JevJudge } from "./index.js";
|
|
11
|
+
import type { JevJudgeConfig } from "./types.js";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Task intake configuration
|
|
15
|
+
*/
|
|
16
|
+
export interface TaskIntakeConfig extends JevJudgeConfig {
|
|
17
|
+
/** Minimum noul threshold for capturing as actionable */
|
|
18
|
+
actionableThreshold?: number;
|
|
19
|
+
/** Dispatch function to send messages to the agent */
|
|
20
|
+
dispatch: (content: string) => Promise<void>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Result of task intake evaluation
|
|
25
|
+
*/
|
|
26
|
+
export interface TaskIntakeResult {
|
|
27
|
+
/** Whether the request should be captured as actionable work */
|
|
28
|
+
actionable: boolean;
|
|
29
|
+
/** Probability that it's actionable (0-1) */
|
|
30
|
+
probability: number;
|
|
31
|
+
/** Whether it duplicates existing work */
|
|
32
|
+
isDuplicate: boolean;
|
|
33
|
+
/** Whether it modifies an existing requirement */
|
|
34
|
+
modifiesExisting: boolean;
|
|
35
|
+
/** Recommended action */
|
|
36
|
+
action: "create" | "skip" | "amend" | "investigate";
|
|
37
|
+
/** Existing todo ID if duplicate/modifies */
|
|
38
|
+
existingTodoId?: number;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Task Intake Controller
|
|
43
|
+
*
|
|
44
|
+
* Evaluates user requests with Jev to determine if they should be
|
|
45
|
+
* captured as todos for automatic tracking.
|
|
46
|
+
*/
|
|
47
|
+
export class TaskIntakeController {
|
|
48
|
+
private jev: JevJudge | null = null;
|
|
49
|
+
private config: Required<TaskIntakeConfig>;
|
|
50
|
+
private lastRequest: string | null = null;
|
|
51
|
+
private lastResult: TaskIntakeResult | null = null;
|
|
52
|
+
|
|
53
|
+
constructor(config: TaskIntakeConfig) {
|
|
54
|
+
this.config = {
|
|
55
|
+
actionableThreshold: 0.7,
|
|
56
|
+
...config,
|
|
57
|
+
} as Required<TaskIntakeConfig>;
|
|
58
|
+
|
|
59
|
+
// Initialize Jev if API key is available
|
|
60
|
+
if (config.apiKey) {
|
|
61
|
+
this.jev = new JevJudge(config as JevJudgeConfig);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Check if Jev is available
|
|
67
|
+
*/
|
|
68
|
+
isAvailable(): boolean {
|
|
69
|
+
return this.jev !== null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Evaluate a user request for task capture
|
|
74
|
+
*/
|
|
75
|
+
async evaluate(
|
|
76
|
+
userMessage: string,
|
|
77
|
+
existingTodos: string[] = []
|
|
78
|
+
): Promise<TaskIntakeResult> {
|
|
79
|
+
return this._evaluate(userMessage, existingTodos);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Evaluate with actual todo items from continuation controller
|
|
84
|
+
*/
|
|
85
|
+
async evaluateWithTodos(
|
|
86
|
+
userMessage: string,
|
|
87
|
+
controller: { getSnapshot: () => Promise<{ items: Array<{ id: number; subject: string; status: string }> }> }
|
|
88
|
+
): Promise<TaskIntakeResult> {
|
|
89
|
+
// Get actual todos from the continuation controller
|
|
90
|
+
let existingTodos: string[] = [];
|
|
91
|
+
try {
|
|
92
|
+
const snapshot = await controller.getSnapshot();
|
|
93
|
+
existingTodos = snapshot.items
|
|
94
|
+
.filter((i) => i.status !== "completed" && i.status !== "deleted")
|
|
95
|
+
.map((i) => i.subject);
|
|
96
|
+
} catch {
|
|
97
|
+
// Ignore errors, use empty list
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return this._evaluate(userMessage, existingTodos);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Internal evaluation logic
|
|
105
|
+
*/
|
|
106
|
+
private async _evaluate(
|
|
107
|
+
userMessage: string,
|
|
108
|
+
existingTodos: string[]
|
|
109
|
+
): Promise<TaskIntakeResult> {
|
|
110
|
+
// Deduplicate identical requests
|
|
111
|
+
if (userMessage === this.lastRequest && this.lastResult) {
|
|
112
|
+
return this.lastResult;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Build state for Jev evaluation
|
|
116
|
+
const state = {
|
|
117
|
+
userMessage,
|
|
118
|
+
existingTodos,
|
|
119
|
+
requestCount: existingTodos.length,
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
// If Jev is not available, use simple heuristic
|
|
123
|
+
if (!this.jev) {
|
|
124
|
+
const result = this.simpleHeuristic(userMessage, existingTodos);
|
|
125
|
+
this.lastRequest = userMessage;
|
|
126
|
+
this.lastResult = result;
|
|
127
|
+
return result;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
try {
|
|
131
|
+
// Ask Jev multiple questions
|
|
132
|
+
const result = await this.jev.evaluate(state, {
|
|
133
|
+
is_actionable: {
|
|
134
|
+
type: "noul",
|
|
135
|
+
instructions:
|
|
136
|
+
"Does the user message request concrete work or a specific outcome? Ignore greetings, thanks, or status questions.",
|
|
137
|
+
},
|
|
138
|
+
is_duplicate: {
|
|
139
|
+
type: "noul",
|
|
140
|
+
instructions:
|
|
141
|
+
"Does the user message duplicate work already listed in existingTodos? Check for similar task names or goals.",
|
|
142
|
+
},
|
|
143
|
+
modifies_existing: {
|
|
144
|
+
type: "noul",
|
|
145
|
+
instructions:
|
|
146
|
+
"Does the user message modify or clarify an existing requirement rather than add new work?",
|
|
147
|
+
},
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
const actionable = result.decisions.is_actionable.response.type === "noul"
|
|
151
|
+
? result.decisions.is_actionable.response.noul >= this.config.actionableThreshold
|
|
152
|
+
: false;
|
|
153
|
+
const isDuplicate = result.decisions.is_duplicate.response.type === "noul"
|
|
154
|
+
? result.decisions.is_duplicate.response.noul >= 0.5
|
|
155
|
+
: false;
|
|
156
|
+
const modifiesExisting = result.decisions.modifies_existing.response.type === "noul"
|
|
157
|
+
? result.decisions.modifies_existing.response.noul >= 0.5
|
|
158
|
+
: false;
|
|
159
|
+
|
|
160
|
+
// Determine recommended action
|
|
161
|
+
let action: TaskIntakeResult["action"];
|
|
162
|
+
if (!actionable) {
|
|
163
|
+
action = "skip";
|
|
164
|
+
} else if (modifiesExisting) {
|
|
165
|
+
action = "amend";
|
|
166
|
+
} else if (isDuplicate) {
|
|
167
|
+
action = "skip";
|
|
168
|
+
} else {
|
|
169
|
+
action = "create";
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const finalResult: TaskIntakeResult = {
|
|
173
|
+
actionable,
|
|
174
|
+
probability: result.decisions.is_actionable.response.type === "noul"
|
|
175
|
+
? result.decisions.is_actionable.response.noul
|
|
176
|
+
: 0,
|
|
177
|
+
isDuplicate,
|
|
178
|
+
modifiesExisting,
|
|
179
|
+
action,
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
this.lastRequest = userMessage;
|
|
183
|
+
this.lastResult = finalResult;
|
|
184
|
+
return finalResult;
|
|
185
|
+
} catch (err) {
|
|
186
|
+
// On Jev error, fall back to simple heuristic
|
|
187
|
+
const result = this.simpleHeuristic(userMessage, existingTodos);
|
|
188
|
+
this.lastRequest = userMessage;
|
|
189
|
+
this.lastResult = result;
|
|
190
|
+
return result;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Simple heuristic when Jev is not available
|
|
196
|
+
*/
|
|
197
|
+
private simpleHeuristic(
|
|
198
|
+
userMessage: string,
|
|
199
|
+
existingTodos: string[]
|
|
200
|
+
): TaskIntakeResult {
|
|
201
|
+
const lower = userMessage.toLowerCase();
|
|
202
|
+
|
|
203
|
+
// Check for non-actionable patterns
|
|
204
|
+
const nonActionable = [
|
|
205
|
+
/^(hi|hello|hey|thanks?|thank you)/i,
|
|
206
|
+
/how are you/i,
|
|
207
|
+
/what('s| is) the status/i,
|
|
208
|
+
/can you help me/i,
|
|
209
|
+
/just checking/i,
|
|
210
|
+
];
|
|
211
|
+
|
|
212
|
+
for (const pattern of nonActionable) {
|
|
213
|
+
if (pattern.test(lower)) {
|
|
214
|
+
return {
|
|
215
|
+
actionable: false,
|
|
216
|
+
probability: 0,
|
|
217
|
+
isDuplicate: false,
|
|
218
|
+
modifiesExisting: false,
|
|
219
|
+
action: "skip",
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Check for actionable patterns
|
|
225
|
+
const actionablePatterns = [
|
|
226
|
+
/\bfix\b/i,
|
|
227
|
+
/\bimplement\b/i,
|
|
228
|
+
/\badd\b/i,
|
|
229
|
+
/\bremove\b/i,
|
|
230
|
+
/\bcreate\b/i,
|
|
231
|
+
/\bupdate\b/i,
|
|
232
|
+
/\bchange\b/i,
|
|
233
|
+
/\bplease\b/i,
|
|
234
|
+
/\bi need\b/i,
|
|
235
|
+
/\bi want\b/i,
|
|
236
|
+
/\bcan you\b/i,
|
|
237
|
+
/\bcould you\b/i,
|
|
238
|
+
];
|
|
239
|
+
|
|
240
|
+
let probability = 0;
|
|
241
|
+
for (const pattern of actionablePatterns) {
|
|
242
|
+
if (pattern.test(lower)) {
|
|
243
|
+
probability = Math.min(probability + 0.2, 0.8);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Check for duplicates
|
|
248
|
+
let isDuplicate = false;
|
|
249
|
+
for (const todo of existingTodos) {
|
|
250
|
+
const todoLower = todo.toLowerCase();
|
|
251
|
+
// Simple word overlap check
|
|
252
|
+
const words = lower.split(/\s+/).filter(w => w.length > 3);
|
|
253
|
+
const overlap = words.filter(w => todoLower.includes(w)).length;
|
|
254
|
+
if (overlap >= 2) {
|
|
255
|
+
isDuplicate = true;
|
|
256
|
+
break;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const actionable = probability >= 0.3 && !isDuplicate;
|
|
261
|
+
|
|
262
|
+
return {
|
|
263
|
+
actionable,
|
|
264
|
+
probability,
|
|
265
|
+
isDuplicate,
|
|
266
|
+
modifiesExisting: false,
|
|
267
|
+
action: actionable ? "create" : "skip",
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Dispatch a capture request to the agent
|
|
273
|
+
* @param userMessage - The original user message to capture
|
|
274
|
+
* @param customType - Optional custom type for the message (default: "task-capture")
|
|
275
|
+
*/
|
|
276
|
+
async requestCapture(
|
|
277
|
+
userMessage: string,
|
|
278
|
+
customType: string = "task-capture"
|
|
279
|
+
): Promise<void> {
|
|
280
|
+
// Build a message to prompt the agent to create todos
|
|
281
|
+
let message = `[${customType}] Work detected in user request: "${userMessage.slice(0, 200)}"`;
|
|
282
|
+
|
|
283
|
+
message += "\n\nPlease capture this work in the todo list if not already tracked.";
|
|
284
|
+
|
|
285
|
+
await this.config.dispatch(message);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Reset state
|
|
290
|
+
*/
|
|
291
|
+
reset(): void {
|
|
292
|
+
this.lastRequest = null;
|
|
293
|
+
this.lastResult = null;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Create a task intake controller
|
|
299
|
+
*/
|
|
300
|
+
export function createTaskIntake(config: TaskIntakeConfig): TaskIntakeController {
|
|
301
|
+
return new TaskIntakeController(config);
|
|
302
|
+
}
|