crewly 1.9.0 → 1.10.0
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/config/roles/orchestrator/prompt.md +20 -2
- package/config/skills/agent/core/get-sops/SKILL.md +6 -2
- package/config/skills/agent/core/get-sops/execute.sh +26 -1
- package/config/skills/agent/core/get-team-norms/SKILL.md +69 -0
- package/config/skills/agent/core/update-sop/SKILL.md +73 -0
- package/config/skills/agent/core/update-sop/execute.sh +115 -0
- package/config/skills/agent/core/update-team-norm/SKILL.md +67 -0
- package/dist/backend/backend/src/controllers/chat-v2/chat-v2.controller.d.ts +2 -0
- package/dist/backend/backend/src/controllers/chat-v2/chat-v2.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/chat-v2/chat-v2.controller.js +50 -1
- package/dist/backend/backend/src/controllers/chat-v2/chat-v2.controller.js.map +1 -1
- package/dist/backend/backend/src/controllers/chat-v2/chat-v2.routes.d.ts +2 -0
- package/dist/backend/backend/src/controllers/chat-v2/chat-v2.routes.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/chat-v2/chat-v2.routes.js +6 -2
- package/dist/backend/backend/src/controllers/chat-v2/chat-v2.routes.js.map +1 -1
- package/dist/backend/backend/src/controllers/slack/slack.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/slack/slack.controller.js +10 -0
- package/dist/backend/backend/src/controllers/slack/slack.controller.js.map +1 -1
- package/dist/backend/backend/src/controllers/system/system.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/system/system.controller.js +2 -1
- package/dist/backend/backend/src/controllers/system/system.controller.js.map +1 -1
- package/dist/backend/backend/src/controllers/team/team.controller.d.ts +17 -0
- package/dist/backend/backend/src/controllers/team/team.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/team/team.controller.js +32 -0
- package/dist/backend/backend/src/controllers/team/team.controller.js.map +1 -1
- package/dist/backend/backend/src/controllers/wiki/wiki.controller.d.ts +45 -0
- package/dist/backend/backend/src/controllers/wiki/wiki.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/wiki/wiki.controller.js +364 -46
- package/dist/backend/backend/src/controllers/wiki/wiki.controller.js.map +1 -1
- package/dist/backend/backend/src/controllers/wiki/wiki.routes.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/wiki/wiki.routes.js +9 -1
- package/dist/backend/backend/src/controllers/wiki/wiki.routes.js.map +1 -1
- package/dist/backend/backend/src/index.d.ts.map +1 -1
- package/dist/backend/backend/src/index.js +44 -70
- package/dist/backend/backend/src/index.js.map +1 -1
- package/dist/backend/backend/src/routes/api.routes.d.ts.map +1 -1
- package/dist/backend/backend/src/routes/api.routes.js +12 -6
- package/dist/backend/backend/src/routes/api.routes.js.map +1 -1
- package/dist/backend/backend/src/services/chat-v2/chat-v2.dispatcher.service.d.ts +13 -4
- package/dist/backend/backend/src/services/chat-v2/chat-v2.dispatcher.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/chat-v2/chat-v2.dispatcher.service.js +47 -11
- package/dist/backend/backend/src/services/chat-v2/chat-v2.dispatcher.service.js.map +1 -1
- package/dist/backend/backend/src/services/chat-v2/chat-v2.providers.d.ts +28 -0
- package/dist/backend/backend/src/services/chat-v2/chat-v2.providers.d.ts.map +1 -1
- package/dist/backend/backend/src/services/chat-v2/chat-v2.providers.js +49 -0
- package/dist/backend/backend/src/services/chat-v2/chat-v2.providers.js.map +1 -1
- package/dist/backend/backend/src/services/chat-v2/chat-v2.service.d.ts +60 -2
- package/dist/backend/backend/src/services/chat-v2/chat-v2.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/chat-v2/chat-v2.service.js +93 -3
- package/dist/backend/backend/src/services/chat-v2/chat-v2.service.js.map +1 -1
- package/dist/backend/backend/src/services/chat-v2/legacy-dto.utils.d.ts +25 -0
- package/dist/backend/backend/src/services/chat-v2/legacy-dto.utils.d.ts.map +1 -1
- package/dist/backend/backend/src/services/chat-v2/legacy-dto.utils.js +33 -0
- package/dist/backend/backend/src/services/chat-v2/legacy-dto.utils.js.map +1 -1
- package/dist/backend/backend/src/services/chat-v2/sqlite/channel.store.d.ts +28 -0
- package/dist/backend/backend/src/services/chat-v2/sqlite/channel.store.d.ts.map +1 -1
- package/dist/backend/backend/src/services/chat-v2/sqlite/channel.store.js +48 -0
- package/dist/backend/backend/src/services/chat-v2/sqlite/channel.store.js.map +1 -1
- package/dist/backend/backend/src/services/chat-v2/sqlite/message.store.d.ts +21 -0
- package/dist/backend/backend/src/services/chat-v2/sqlite/message.store.d.ts.map +1 -1
- package/dist/backend/backend/src/services/chat-v2/sqlite/message.store.js +30 -0
- package/dist/backend/backend/src/services/chat-v2/sqlite/message.store.js.map +1 -1
- package/dist/backend/backend/src/services/chat-v2/types.d.ts +14 -0
- package/dist/backend/backend/src/services/chat-v2/types.d.ts.map +1 -1
- package/dist/backend/backend/src/services/chat-v2/types.js.map +1 -1
- package/dist/backend/backend/src/services/index.d.ts +0 -1
- package/dist/backend/backend/src/services/index.d.ts.map +1 -1
- package/dist/backend/backend/src/services/index.js +0 -1
- package/dist/backend/backend/src/services/index.js.map +1 -1
- package/dist/backend/backend/src/services/intent-task/intent-task-follow-up.service.d.ts +26 -108
- package/dist/backend/backend/src/services/intent-task/intent-task-follow-up.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/intent-task/intent-task-follow-up.service.js +26 -214
- package/dist/backend/backend/src/services/intent-task/intent-task-follow-up.service.js.map +1 -1
- package/dist/backend/backend/src/services/reconciler/reconciler-data-provider.d.ts +11 -0
- package/dist/backend/backend/src/services/reconciler/reconciler-data-provider.d.ts.map +1 -1
- package/dist/backend/backend/src/services/reconciler/reconciler-data-provider.js +36 -0
- package/dist/backend/backend/src/services/reconciler/reconciler-data-provider.js.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-orchestrator-bridge.d.ts +10 -0
- package/dist/backend/backend/src/services/slack/slack-orchestrator-bridge.d.ts.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-orchestrator-bridge.js +21 -0
- package/dist/backend/backend/src/services/slack/slack-orchestrator-bridge.js.map +1 -1
- package/dist/backend/backend/src/services/slack/slack.service.d.ts +12 -0
- package/dist/backend/backend/src/services/slack/slack.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/slack/slack.service.js +53 -1
- package/dist/backend/backend/src/services/slack/slack.service.js.map +1 -1
- package/dist/backend/backend/src/services/sop/sop.service.d.ts +10 -0
- package/dist/backend/backend/src/services/sop/sop.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/sop/sop.service.js +71 -10
- package/dist/backend/backend/src/services/sop/sop.service.js.map +1 -1
- package/dist/backend/backend/src/services/wiki/sop-catalog.service.d.ts +83 -0
- package/dist/backend/backend/src/services/wiki/sop-catalog.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/wiki/sop-catalog.service.js +185 -0
- package/dist/backend/backend/src/services/wiki/sop-catalog.service.js.map +1 -0
- package/dist/backend/backend/src/services/wiki/wiki-cleanup.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/wiki/wiki-cleanup.service.js +12 -0
- package/dist/backend/backend/src/services/wiki/wiki-cleanup.service.js.map +1 -1
- package/dist/backend/backend/src/services/wiki/wiki-overlay.resolver.d.ts +43 -0
- package/dist/backend/backend/src/services/wiki/wiki-overlay.resolver.d.ts.map +1 -0
- package/dist/backend/backend/src/services/wiki/wiki-overlay.resolver.js +67 -0
- package/dist/backend/backend/src/services/wiki/wiki-overlay.resolver.js.map +1 -0
- package/dist/backend/backend/src/services/wiki/wiki-search.service.d.ts +79 -19
- package/dist/backend/backend/src/services/wiki/wiki-search.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/wiki/wiki-search.service.js +275 -106
- package/dist/backend/backend/src/services/wiki/wiki-search.service.js.map +1 -1
- package/dist/backend/backend/src/services/wiki/wiki-workitem-bridge.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/wiki/wiki-workitem-bridge.service.js +13 -0
- package/dist/backend/backend/src/services/wiki/wiki-workitem-bridge.service.js.map +1 -1
- package/dist/backend/backend/src/services/workflow/cron-task.service.d.ts +13 -0
- package/dist/backend/backend/src/services/workflow/cron-task.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/workflow/cron-task.service.js +43 -3
- package/dist/backend/backend/src/services/workflow/cron-task.service.js.map +1 -1
- package/dist/backend/backend/src/types/sop.types.d.ts +7 -0
- package/dist/backend/backend/src/types/sop.types.d.ts.map +1 -1
- package/dist/backend/backend/src/types/sop.types.js.map +1 -1
- package/dist/backend/backend/src/types/v2/work-item.types.d.ts +7 -0
- package/dist/backend/backend/src/types/v2/work-item.types.d.ts.map +1 -1
- package/dist/backend/backend/src/types/v2/work-item.types.js +1 -1
- package/dist/backend/backend/src/types/v2/work-item.types.js.map +1 -1
- package/dist/cli/backend/src/types/sop.types.d.ts +7 -0
- package/dist/cli/backend/src/types/sop.types.d.ts.map +1 -1
- package/dist/cli/backend/src/types/sop.types.js.map +1 -1
- package/dist/cli/backend/src/types/v2/work-item.types.d.ts +7 -0
- package/dist/cli/backend/src/types/v2/work-item.types.d.ts.map +1 -1
- package/dist/cli/backend/src/types/v2/work-item.types.js +1 -1
- package/dist/cli/backend/src/types/v2/work-item.types.js.map +1 -1
- package/frontend/dist/assets/index-4099a91c.js +4961 -0
- package/frontend/dist/assets/index-44266b5d.css +42 -0
- package/frontend/dist/index.html +2 -2
- package/package.json +1 -1
- package/frontend/dist/assets/index-068bb4f6.css +0 -42
- package/frontend/dist/assets/index-c24ceb15.js +0 -4960
|
@@ -1,138 +1,56 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Intent Task Follow-Up Service
|
|
2
|
+
* Intent Task Follow-Up Service — RETIRED to a no-op (2026-06-02).
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* the
|
|
4
|
+
* This service used to schedule periodic follow-up reminders for unresolved
|
|
5
|
+
* intent tasks via `UnifiedSchedulerService`. That mechanism was dead-on-arrival:
|
|
6
|
+
* the UnifiedScheduler poll loop was never started and nothing listened for its
|
|
7
|
+
* `triggered` event, so a scheduled follow-up never actually fired. When
|
|
8
|
+
* UnifiedSchedulerService was retired (in favour of the v3 TriggerEngine), this
|
|
9
|
+
* service was reduced to a no-op shell that preserves its public API so the
|
|
10
|
+
* (lazy) call sites in {@link IntentTaskService} keep compiling without change.
|
|
7
11
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
12
|
+
* Stuck/unresolved work is now handled by the WorkItem + reconciler autonomy
|
|
13
|
+
* mesh (task pool → dispatch / auto-claim / reconciler self-heal), so there is
|
|
14
|
+
* no behaviour to restore here. If task-level follow-up reminders are wanted
|
|
15
|
+
* again, build them on the TriggerEngine (time/interval trigger → WorkItem),
|
|
16
|
+
* not on a separate scheduler.
|
|
13
17
|
*
|
|
14
18
|
* @module services/intent-task/intent-task-follow-up.service
|
|
15
19
|
*/
|
|
16
20
|
import { EventEmitter } from 'events';
|
|
17
|
-
import { IntentTaskService } from './intent-task.service.js';
|
|
18
|
-
import { UnifiedSchedulerService } from '../workflow/unified-scheduler.service.js';
|
|
19
21
|
import type { IntentTaskStatus } from '../../types/intent-task.types.js';
|
|
20
22
|
/**
|
|
21
|
-
* Payload emitted when a follow-up
|
|
23
|
+
* Payload that used to be emitted when a follow-up triggered. Retained for
|
|
24
|
+
* backwards-compatible imports; no longer emitted.
|
|
22
25
|
*/
|
|
23
26
|
export interface FollowUpNeededPayload {
|
|
24
|
-
/** The task ID that needs attention */
|
|
25
27
|
taskId: string;
|
|
26
|
-
/** The original intent description */
|
|
27
28
|
intent: string;
|
|
28
|
-
/** Current task status */
|
|
29
29
|
status: IntentTaskStatus;
|
|
30
|
-
/** Agent sessions assigned to this task */
|
|
31
30
|
assignedSessions: string[];
|
|
32
31
|
}
|
|
33
32
|
/**
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
* IntentTaskService for task state.
|
|
37
|
-
*
|
|
38
|
-
* Emits 'follow-up-needed' events when a follow-up triggers and the
|
|
39
|
-
* associated task is still unresolved, allowing the orchestrator to
|
|
40
|
-
* take action.
|
|
41
|
-
*
|
|
42
|
-
* @example
|
|
43
|
-
* ```typescript
|
|
44
|
-
* const service = IntentTaskFollowUpService.getInstance();
|
|
45
|
-
* service.on('follow-up-needed', (payload) => {
|
|
46
|
-
* console.log(`Task ${payload.taskId} still unresolved`);
|
|
47
|
-
* });
|
|
48
|
-
* service.scheduleFollowUp('task-123');
|
|
49
|
-
* ```
|
|
33
|
+
* No-op shell of the former follow-up scheduler. Public methods are preserved
|
|
34
|
+
* so existing callers compile unchanged; none of them schedule anything.
|
|
50
35
|
*/
|
|
51
36
|
export declare class IntentTaskFollowUpService extends EventEmitter {
|
|
52
37
|
private static instance;
|
|
53
|
-
|
|
54
|
-
private readonly schedulerService;
|
|
55
|
-
/**
|
|
56
|
-
* Create a new IntentTaskFollowUpService.
|
|
57
|
-
*
|
|
58
|
-
* @param intentTaskService - Override for IntentTaskService (used in tests)
|
|
59
|
-
* @param schedulerService - Override for UnifiedSchedulerService (used in tests)
|
|
60
|
-
*/
|
|
61
|
-
constructor(intentTaskService?: IntentTaskService, schedulerService?: UnifiedSchedulerService);
|
|
62
|
-
/**
|
|
63
|
-
* Get the singleton instance.
|
|
64
|
-
*
|
|
65
|
-
* @returns Shared IntentTaskFollowUpService instance
|
|
66
|
-
*/
|
|
38
|
+
/** Get the singleton instance. */
|
|
67
39
|
static getInstance(): IntentTaskFollowUpService;
|
|
68
|
-
/**
|
|
69
|
-
* Reset the singleton (for testing).
|
|
70
|
-
*/
|
|
40
|
+
/** Reset the singleton (for testing). */
|
|
71
41
|
static resetInstance(): void;
|
|
72
42
|
/**
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
* Creates an interval schedule in UnifiedSchedulerService that will
|
|
76
|
-
* trigger periodically. The schedule ID is stored on the task for
|
|
77
|
-
* later cancellation.
|
|
78
|
-
*
|
|
79
|
-
* @param taskId - The intent task ID to follow up on
|
|
80
|
-
* @param delayMinutes - Minutes between follow-up checks (default: 15)
|
|
81
|
-
* @returns The schedule ID, or null if the task is not found or already terminal
|
|
82
|
-
* @throws Error if the task does not exist
|
|
83
|
-
*/
|
|
84
|
-
scheduleFollowUp(taskId: string, delayMinutes?: number): string | null;
|
|
85
|
-
/**
|
|
86
|
-
* Handle a follow-up trigger from the scheduler.
|
|
87
|
-
*
|
|
88
|
-
* Checks the associated task's status:
|
|
89
|
-
* - If terminal (completed/failed/cancelled): cancels the schedule
|
|
90
|
-
* - If still unresolved: emits 'follow-up-needed' event
|
|
91
|
-
*
|
|
92
|
-
* @param scheduleId - The schedule ID that was triggered
|
|
93
|
-
*/
|
|
94
|
-
onFollowUpTriggered(scheduleId: string): void;
|
|
95
|
-
/**
|
|
96
|
-
* Restore follow-ups after a service restart.
|
|
97
|
-
*
|
|
98
|
-
* Scans all unresolved tasks and ensures each has an active follow-up
|
|
99
|
-
* schedule. For tasks with a scheduleId, verifies the schedule is still
|
|
100
|
-
* active in UnifiedSchedulerService. For unresolved tasks without a
|
|
101
|
-
* scheduleId, creates a new follow-up.
|
|
102
|
-
*
|
|
103
|
-
* @param delayMinutes - Minutes between follow-up checks (default: 15)
|
|
104
|
-
* @returns Number of follow-ups restored or created
|
|
105
|
-
*/
|
|
106
|
-
restoreFollowUps(delayMinutes?: number): number;
|
|
107
|
-
/**
|
|
108
|
-
* Cancel the follow-up schedule for a task.
|
|
109
|
-
*
|
|
110
|
-
* Removes the schedule from UnifiedSchedulerService and clears the
|
|
111
|
-
* scheduleId on the task.
|
|
112
|
-
*
|
|
113
|
-
* @param taskId - The intent task ID
|
|
114
|
-
* @returns True if a follow-up was cancelled, false if none existed
|
|
115
|
-
*/
|
|
116
|
-
cancelFollowUp(taskId: string): boolean;
|
|
117
|
-
/**
|
|
118
|
-
* Find a task by its associated schedule ID.
|
|
119
|
-
*
|
|
120
|
-
* @param scheduleId - The schedule ID to search for
|
|
121
|
-
* @returns The matching task, or null if not found
|
|
122
|
-
*/
|
|
123
|
-
private findTaskByScheduleId;
|
|
124
|
-
/**
|
|
125
|
-
* Cancel a follow-up by schedule ID and clear the task association.
|
|
43
|
+
* No-op. Previously created an interval schedule in UnifiedSchedulerService
|
|
44
|
+
* (which never fired). Returns null — no schedule is created.
|
|
126
45
|
*
|
|
127
|
-
* @
|
|
128
|
-
* @param scheduleId - The schedule ID to delete
|
|
46
|
+
* @returns Always null
|
|
129
47
|
*/
|
|
130
|
-
|
|
48
|
+
scheduleFollowUp(_taskId: string, _delayMinutes?: number): string | null;
|
|
131
49
|
/**
|
|
132
|
-
*
|
|
50
|
+
* No-op. Previously cancelled a task's follow-up schedule.
|
|
133
51
|
*
|
|
134
|
-
* @returns
|
|
52
|
+
* @returns Always false (nothing to cancel)
|
|
135
53
|
*/
|
|
136
|
-
|
|
54
|
+
cancelFollowUp(_taskId: string): boolean;
|
|
137
55
|
}
|
|
138
56
|
//# sourceMappingURL=intent-task-follow-up.service.d.ts.map
|
package/dist/backend/backend/src/services/intent-task/intent-task-follow-up.service.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"intent-task-follow-up.service.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/intent-task/intent-task-follow-up.service.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"intent-task-follow-up.service.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/intent-task/intent-task-follow-up.service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAC;AAEzE;;;GAGG;AACH,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,gBAAgB,CAAC;IACzB,gBAAgB,EAAE,MAAM,EAAE,CAAC;CAC5B;AAED;;;GAGG;AACH,qBAAa,yBAA0B,SAAQ,YAAY;IACzD,OAAO,CAAC,MAAM,CAAC,QAAQ,CAA0C;IAEjE,kCAAkC;IAClC,MAAM,CAAC,WAAW,IAAI,yBAAyB;IAO/C,yCAAyC;IACzC,MAAM,CAAC,aAAa,IAAI,IAAI;IAI5B;;;;;OAKG;IACH,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAIxE;;;;OAIG;IACH,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO;CAGzC"}
|
|
@@ -1,244 +1,56 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Intent Task Follow-Up Service
|
|
2
|
+
* Intent Task Follow-Up Service — RETIRED to a no-op (2026-06-02).
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* the
|
|
4
|
+
* This service used to schedule periodic follow-up reminders for unresolved
|
|
5
|
+
* intent tasks via `UnifiedSchedulerService`. That mechanism was dead-on-arrival:
|
|
6
|
+
* the UnifiedScheduler poll loop was never started and nothing listened for its
|
|
7
|
+
* `triggered` event, so a scheduled follow-up never actually fired. When
|
|
8
|
+
* UnifiedSchedulerService was retired (in favour of the v3 TriggerEngine), this
|
|
9
|
+
* service was reduced to a no-op shell that preserves its public API so the
|
|
10
|
+
* (lazy) call sites in {@link IntentTaskService} keep compiling without change.
|
|
7
11
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
12
|
+
* Stuck/unresolved work is now handled by the WorkItem + reconciler autonomy
|
|
13
|
+
* mesh (task pool → dispatch / auto-claim / reconciler self-heal), so there is
|
|
14
|
+
* no behaviour to restore here. If task-level follow-up reminders are wanted
|
|
15
|
+
* again, build them on the TriggerEngine (time/interval trigger → WorkItem),
|
|
16
|
+
* not on a separate scheduler.
|
|
13
17
|
*
|
|
14
18
|
* @module services/intent-task/intent-task-follow-up.service
|
|
15
19
|
*/
|
|
16
20
|
import { EventEmitter } from 'events';
|
|
17
|
-
import { IntentTaskService } from './intent-task.service.js';
|
|
18
|
-
import { UnifiedSchedulerService } from '../workflow/unified-scheduler.service.js';
|
|
19
|
-
// =============================================================================
|
|
20
|
-
// Constants
|
|
21
|
-
// =============================================================================
|
|
22
|
-
/** Default follow-up delay in minutes */
|
|
23
|
-
const DEFAULT_FOLLOW_UP_DELAY_MINUTES = 15;
|
|
24
|
-
/** Target session for follow-up schedule messages (orchestrator) */
|
|
25
|
-
const FOLLOW_UP_TARGET = 'crewly-orc';
|
|
26
|
-
/** Terminal statuses that should cancel follow-ups */
|
|
27
|
-
const TERMINAL_STATUSES = new Set([
|
|
28
|
-
'completed',
|
|
29
|
-
'failed',
|
|
30
|
-
'cancelled',
|
|
31
|
-
]);
|
|
32
|
-
// =============================================================================
|
|
33
|
-
// Service
|
|
34
|
-
// =============================================================================
|
|
35
21
|
/**
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
* IntentTaskService for task state.
|
|
39
|
-
*
|
|
40
|
-
* Emits 'follow-up-needed' events when a follow-up triggers and the
|
|
41
|
-
* associated task is still unresolved, allowing the orchestrator to
|
|
42
|
-
* take action.
|
|
43
|
-
*
|
|
44
|
-
* @example
|
|
45
|
-
* ```typescript
|
|
46
|
-
* const service = IntentTaskFollowUpService.getInstance();
|
|
47
|
-
* service.on('follow-up-needed', (payload) => {
|
|
48
|
-
* console.log(`Task ${payload.taskId} still unresolved`);
|
|
49
|
-
* });
|
|
50
|
-
* service.scheduleFollowUp('task-123');
|
|
51
|
-
* ```
|
|
22
|
+
* No-op shell of the former follow-up scheduler. Public methods are preserved
|
|
23
|
+
* so existing callers compile unchanged; none of them schedule anything.
|
|
52
24
|
*/
|
|
53
25
|
export class IntentTaskFollowUpService extends EventEmitter {
|
|
54
26
|
static instance = null;
|
|
55
|
-
|
|
56
|
-
schedulerService;
|
|
57
|
-
/**
|
|
58
|
-
* Create a new IntentTaskFollowUpService.
|
|
59
|
-
*
|
|
60
|
-
* @param intentTaskService - Override for IntentTaskService (used in tests)
|
|
61
|
-
* @param schedulerService - Override for UnifiedSchedulerService (used in tests)
|
|
62
|
-
*/
|
|
63
|
-
constructor(intentTaskService, schedulerService) {
|
|
64
|
-
super();
|
|
65
|
-
this.intentTaskService = intentTaskService ?? IntentTaskService.getInstance();
|
|
66
|
-
this.schedulerService = schedulerService ?? UnifiedSchedulerService.getInstance();
|
|
67
|
-
}
|
|
68
|
-
/**
|
|
69
|
-
* Get the singleton instance.
|
|
70
|
-
*
|
|
71
|
-
* @returns Shared IntentTaskFollowUpService instance
|
|
72
|
-
*/
|
|
27
|
+
/** Get the singleton instance. */
|
|
73
28
|
static getInstance() {
|
|
74
29
|
if (!IntentTaskFollowUpService.instance) {
|
|
75
30
|
IntentTaskFollowUpService.instance = new IntentTaskFollowUpService();
|
|
76
31
|
}
|
|
77
32
|
return IntentTaskFollowUpService.instance;
|
|
78
33
|
}
|
|
79
|
-
/**
|
|
80
|
-
* Reset the singleton (for testing).
|
|
81
|
-
*/
|
|
34
|
+
/** Reset the singleton (for testing). */
|
|
82
35
|
static resetInstance() {
|
|
83
36
|
IntentTaskFollowUpService.instance = null;
|
|
84
37
|
}
|
|
85
|
-
// ===========================================================================
|
|
86
|
-
// Public API
|
|
87
|
-
// ===========================================================================
|
|
88
|
-
/**
|
|
89
|
-
* Schedule a follow-up for an unresolved task.
|
|
90
|
-
*
|
|
91
|
-
* Creates an interval schedule in UnifiedSchedulerService that will
|
|
92
|
-
* trigger periodically. The schedule ID is stored on the task for
|
|
93
|
-
* later cancellation.
|
|
94
|
-
*
|
|
95
|
-
* @param taskId - The intent task ID to follow up on
|
|
96
|
-
* @param delayMinutes - Minutes between follow-up checks (default: 15)
|
|
97
|
-
* @returns The schedule ID, or null if the task is not found or already terminal
|
|
98
|
-
* @throws Error if the task does not exist
|
|
99
|
-
*/
|
|
100
|
-
scheduleFollowUp(taskId, delayMinutes) {
|
|
101
|
-
const task = this.intentTaskService.getTask(taskId);
|
|
102
|
-
if (!task) {
|
|
103
|
-
throw new Error(`Task not found: ${taskId}`);
|
|
104
|
-
}
|
|
105
|
-
// Don't schedule for already-terminal tasks
|
|
106
|
-
if (TERMINAL_STATUSES.has(task.status)) {
|
|
107
|
-
return null;
|
|
108
|
-
}
|
|
109
|
-
// If task already has a follow-up schedule, don't create a duplicate
|
|
110
|
-
if (task.scheduleId) {
|
|
111
|
-
const existingSchedule = this.schedulerService.get(task.scheduleId);
|
|
112
|
-
if (existingSchedule && existingSchedule.status === 'active') {
|
|
113
|
-
return task.scheduleId;
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
const interval = delayMinutes ?? DEFAULT_FOLLOW_UP_DELAY_MINUTES;
|
|
117
|
-
const schedule = this.schedulerService.create({
|
|
118
|
-
target: FOLLOW_UP_TARGET,
|
|
119
|
-
type: 'interval',
|
|
120
|
-
intervalMinutes: interval,
|
|
121
|
-
message: `[follow-up] Task ${taskId} is still unresolved. Intent: "${task.intent}". Status: ${task.status}. Please check on it.`,
|
|
122
|
-
enabled: true,
|
|
123
|
-
autoStart: false,
|
|
124
|
-
maxConsecutiveTriggers: 10,
|
|
125
|
-
});
|
|
126
|
-
// Associate the schedule with the task
|
|
127
|
-
this.intentTaskService.setSchedule(taskId, schedule.id);
|
|
128
|
-
return schedule.id;
|
|
129
|
-
}
|
|
130
|
-
/**
|
|
131
|
-
* Handle a follow-up trigger from the scheduler.
|
|
132
|
-
*
|
|
133
|
-
* Checks the associated task's status:
|
|
134
|
-
* - If terminal (completed/failed/cancelled): cancels the schedule
|
|
135
|
-
* - If still unresolved: emits 'follow-up-needed' event
|
|
136
|
-
*
|
|
137
|
-
* @param scheduleId - The schedule ID that was triggered
|
|
138
|
-
*/
|
|
139
|
-
onFollowUpTriggered(scheduleId) {
|
|
140
|
-
// Find the task associated with this schedule
|
|
141
|
-
const task = this.findTaskByScheduleId(scheduleId);
|
|
142
|
-
if (!task) {
|
|
143
|
-
// No task found for this schedule — clean it up
|
|
144
|
-
this.schedulerService.delete(scheduleId);
|
|
145
|
-
return;
|
|
146
|
-
}
|
|
147
|
-
if (TERMINAL_STATUSES.has(task.status)) {
|
|
148
|
-
// Task is done — cancel the follow-up schedule
|
|
149
|
-
this.cancelFollowUpByScheduleId(task.id, scheduleId);
|
|
150
|
-
return;
|
|
151
|
-
}
|
|
152
|
-
// Task is still unresolved — emit event for orchestrator
|
|
153
|
-
const payload = {
|
|
154
|
-
taskId: task.id,
|
|
155
|
-
intent: task.intent,
|
|
156
|
-
status: task.status,
|
|
157
|
-
assignedSessions: [...task.assignedSessions],
|
|
158
|
-
};
|
|
159
|
-
this.emit('follow-up-needed', payload);
|
|
160
|
-
}
|
|
161
|
-
/**
|
|
162
|
-
* Restore follow-ups after a service restart.
|
|
163
|
-
*
|
|
164
|
-
* Scans all unresolved tasks and ensures each has an active follow-up
|
|
165
|
-
* schedule. For tasks with a scheduleId, verifies the schedule is still
|
|
166
|
-
* active in UnifiedSchedulerService. For unresolved tasks without a
|
|
167
|
-
* scheduleId, creates a new follow-up.
|
|
168
|
-
*
|
|
169
|
-
* @param delayMinutes - Minutes between follow-up checks (default: 15)
|
|
170
|
-
* @returns Number of follow-ups restored or created
|
|
171
|
-
*/
|
|
172
|
-
restoreFollowUps(delayMinutes) {
|
|
173
|
-
const unresolvedTasks = this.getUnresolvedTasks();
|
|
174
|
-
let restoredCount = 0;
|
|
175
|
-
for (const task of unresolvedTasks) {
|
|
176
|
-
if (task.scheduleId) {
|
|
177
|
-
// Task has a schedule — check if it's still active
|
|
178
|
-
const schedule = this.schedulerService.get(task.scheduleId);
|
|
179
|
-
if (schedule && schedule.status === 'active') {
|
|
180
|
-
// Schedule is still active, nothing to do
|
|
181
|
-
continue;
|
|
182
|
-
}
|
|
183
|
-
// Schedule is missing or not active — recreate
|
|
184
|
-
}
|
|
185
|
-
// Create (or recreate) a follow-up schedule
|
|
186
|
-
const scheduleId = this.scheduleFollowUp(task.id, delayMinutes);
|
|
187
|
-
if (scheduleId) {
|
|
188
|
-
restoredCount++;
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
return restoredCount;
|
|
192
|
-
}
|
|
193
|
-
/**
|
|
194
|
-
* Cancel the follow-up schedule for a task.
|
|
195
|
-
*
|
|
196
|
-
* Removes the schedule from UnifiedSchedulerService and clears the
|
|
197
|
-
* scheduleId on the task.
|
|
198
|
-
*
|
|
199
|
-
* @param taskId - The intent task ID
|
|
200
|
-
* @returns True if a follow-up was cancelled, false if none existed
|
|
201
|
-
*/
|
|
202
|
-
cancelFollowUp(taskId) {
|
|
203
|
-
const task = this.intentTaskService.getTask(taskId);
|
|
204
|
-
if (!task || !task.scheduleId) {
|
|
205
|
-
return false;
|
|
206
|
-
}
|
|
207
|
-
const scheduleId = task.scheduleId;
|
|
208
|
-
this.schedulerService.delete(scheduleId);
|
|
209
|
-
this.intentTaskService.updateTask(taskId, { scheduleId: undefined });
|
|
210
|
-
return true;
|
|
211
|
-
}
|
|
212
|
-
// ===========================================================================
|
|
213
|
-
// Private Helpers
|
|
214
|
-
// ===========================================================================
|
|
215
|
-
/**
|
|
216
|
-
* Find a task by its associated schedule ID.
|
|
217
|
-
*
|
|
218
|
-
* @param scheduleId - The schedule ID to search for
|
|
219
|
-
* @returns The matching task, or null if not found
|
|
220
|
-
*/
|
|
221
|
-
findTaskByScheduleId(scheduleId) {
|
|
222
|
-
const allTasks = this.intentTaskService.listTasks();
|
|
223
|
-
return allTasks.find((t) => t.scheduleId === scheduleId) ?? null;
|
|
224
|
-
}
|
|
225
38
|
/**
|
|
226
|
-
*
|
|
39
|
+
* No-op. Previously created an interval schedule in UnifiedSchedulerService
|
|
40
|
+
* (which never fired). Returns null — no schedule is created.
|
|
227
41
|
*
|
|
228
|
-
* @
|
|
229
|
-
* @param scheduleId - The schedule ID to delete
|
|
42
|
+
* @returns Always null
|
|
230
43
|
*/
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
this.intentTaskService.updateTask(taskId, { scheduleId: undefined });
|
|
44
|
+
scheduleFollowUp(_taskId, _delayMinutes) {
|
|
45
|
+
return null;
|
|
234
46
|
}
|
|
235
47
|
/**
|
|
236
|
-
*
|
|
48
|
+
* No-op. Previously cancelled a task's follow-up schedule.
|
|
237
49
|
*
|
|
238
|
-
* @returns
|
|
50
|
+
* @returns Always false (nothing to cancel)
|
|
239
51
|
*/
|
|
240
|
-
|
|
241
|
-
return
|
|
52
|
+
cancelFollowUp(_taskId) {
|
|
53
|
+
return false;
|
|
242
54
|
}
|
|
243
55
|
}
|
|
244
56
|
//# sourceMappingURL=intent-task-follow-up.service.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"intent-task-follow-up.service.js","sourceRoot":"","sources":["../../../../../../backend/src/services/intent-task/intent-task-follow-up.service.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"intent-task-follow-up.service.js","sourceRoot":"","sources":["../../../../../../backend/src/services/intent-task/intent-task-follow-up.service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AActC;;;GAGG;AACH,MAAM,OAAO,yBAA0B,SAAQ,YAAY;IACjD,MAAM,CAAC,QAAQ,GAAqC,IAAI,CAAC;IAEjE,kCAAkC;IAClC,MAAM,CAAC,WAAW;QAChB,IAAI,CAAC,yBAAyB,CAAC,QAAQ,EAAE,CAAC;YACxC,yBAAyB,CAAC,QAAQ,GAAG,IAAI,yBAAyB,EAAE,CAAC;QACvE,CAAC;QACD,OAAO,yBAAyB,CAAC,QAAQ,CAAC;IAC5C,CAAC;IAED,yCAAyC;IACzC,MAAM,CAAC,aAAa;QAClB,yBAAyB,CAAC,QAAQ,GAAG,IAAI,CAAC;IAC5C,CAAC;IAED;;;;;OAKG;IACH,gBAAgB,CAAC,OAAe,EAAE,aAAsB;QACtD,OAAO,IAAI,CAAC;IACd,CAAC;IAED;;;;OAIG;IACH,cAAc,CAAC,OAAe;QAC5B,OAAO,KAAK,CAAC;IACf,CAAC"}
|
|
@@ -72,6 +72,17 @@ export declare class LiveReconcilerDataProvider implements ReconcilerDataProvide
|
|
|
72
72
|
private agentRegistration;
|
|
73
73
|
private consecutivePressureSkips;
|
|
74
74
|
private lastPressureNotifiedAt;
|
|
75
|
+
/**
|
|
76
|
+
* Per-WorkItem redeliver cooldown. The reconciler fast loop runs every
|
|
77
|
+
* ~10s and re-emits a `redeliver` wake for any queued WI whose target is
|
|
78
|
+
* active-but-idle — with NO per-WI rate-limit in the rule. Without this
|
|
79
|
+
* gate a queued-but-unclaimed WI (e.g. one the orc has read but not yet
|
|
80
|
+
* claimed) is re-POSTed to its PTY every 10s (~50-60/hr), which is the
|
|
81
|
+
* wiki-bridge "spam" the user saw and the same class as the 2026-05-27
|
|
82
|
+
* PTY-flood incident. We cap redelivery of a given WI to once per
|
|
83
|
+
* {@link REDELIVER_COOLDOWN_MS}.
|
|
84
|
+
*/
|
|
85
|
+
private readonly lastRedeliverAt;
|
|
75
86
|
constructor();
|
|
76
87
|
/**
|
|
77
88
|
* Inject the EventBus used to broadcast `system:memory_pressure` to
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reconciler-data-provider.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/reconciler/reconciler-data-provider.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAC;AACtE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACxD,OAAO,KAAK,EACV,QAAQ,EACR,OAAO,EACP,SAAS,EACT,mBAAmB,EACnB,UAAU,EACX,MAAM,yBAAyB,CAAC;AAWjC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAC;
|
|
1
|
+
{"version":3,"file":"reconciler-data-provider.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/reconciler/reconciler-data-provider.ts"],"names":[],"mappings":"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"}
|
|
@@ -24,6 +24,16 @@ import { WEB_CONSTANTS, AGENT_SUSPEND_CONSTANTS } from '../../constants.js';
|
|
|
24
24
|
// ---------------------------------------------------------------------------
|
|
25
25
|
/** How long an agent can be unseen before we consider it stale (5 min). */
|
|
26
26
|
const AGENT_STALE_THRESHOLD_MS = 5 * 60 * 1000;
|
|
27
|
+
/**
|
|
28
|
+
* Minimum gap between redelivering the SAME WorkItem brief. The reconciler
|
|
29
|
+
* fast loop fires every ~10s; this caps a queued WI's re-POSTs to once per
|
|
30
|
+
* window so an unclaimed-but-read WI can't flood the agent's PTY. Override
|
|
31
|
+
* with `CREWLY_RECONCILER_REDELIVER_COOLDOWN_MS`.
|
|
32
|
+
*/
|
|
33
|
+
const REDELIVER_COOLDOWN_MS = (() => {
|
|
34
|
+
const raw = Number(process.env['CREWLY_RECONCILER_REDELIVER_COOLDOWN_MS']);
|
|
35
|
+
return Number.isFinite(raw) && raw > 0 ? raw : 5 * 60 * 1000; // 5 min
|
|
36
|
+
})();
|
|
27
37
|
/**
|
|
28
38
|
* Heuristic: detect "storage not yet hydrated" errors so the data
|
|
29
39
|
* provider can demote them from `error` to `debug` log level.
|
|
@@ -145,6 +155,17 @@ export class LiveReconcilerDataProvider {
|
|
|
145
155
|
// EventBus deduplicates on event id).
|
|
146
156
|
consecutivePressureSkips = 0;
|
|
147
157
|
lastPressureNotifiedAt = 0;
|
|
158
|
+
/**
|
|
159
|
+
* Per-WorkItem redeliver cooldown. The reconciler fast loop runs every
|
|
160
|
+
* ~10s and re-emits a `redeliver` wake for any queued WI whose target is
|
|
161
|
+
* active-but-idle — with NO per-WI rate-limit in the rule. Without this
|
|
162
|
+
* gate a queued-but-unclaimed WI (e.g. one the orc has read but not yet
|
|
163
|
+
* claimed) is re-POSTed to its PTY every 10s (~50-60/hr), which is the
|
|
164
|
+
* wiki-bridge "spam" the user saw and the same class as the 2026-05-27
|
|
165
|
+
* PTY-flood incident. We cap redelivery of a given WI to once per
|
|
166
|
+
* {@link REDELIVER_COOLDOWN_MS}.
|
|
167
|
+
*/
|
|
168
|
+
lastRedeliverAt = new Map();
|
|
148
169
|
constructor() {
|
|
149
170
|
this.logger = LoggerService.getInstance().createComponentLogger('ReconcilerDataProvider');
|
|
150
171
|
this.storage = StorageService.getInstance();
|
|
@@ -930,10 +951,25 @@ export class LiveReconcilerDataProvider {
|
|
|
930
951
|
workItemId: action.workItemId,
|
|
931
952
|
currentStatus: wi?.status,
|
|
932
953
|
});
|
|
954
|
+
// WI left the queue (claimed/terminal) — drop its cooldown record.
|
|
955
|
+
this.lastRedeliverAt.delete(action.workItemId);
|
|
956
|
+
return false;
|
|
957
|
+
}
|
|
958
|
+
// Per-WI redeliver cooldown — the fast loop re-emits this every ~10s
|
|
959
|
+
// while the WI stays queued; without the gate that floods the PTY.
|
|
960
|
+
const lastAt = this.lastRedeliverAt.get(action.workItemId);
|
|
961
|
+
if (lastAt !== undefined && Date.now() - lastAt < REDELIVER_COOLDOWN_MS) {
|
|
962
|
+
this.logger.debug('Redeliver suppressed — within cooldown', {
|
|
963
|
+
agent: agentSessionName,
|
|
964
|
+
workItemId: action.workItemId,
|
|
965
|
+
sinceMs: Date.now() - lastAt,
|
|
966
|
+
});
|
|
933
967
|
return false;
|
|
934
968
|
}
|
|
935
969
|
const subscriber = WorkItemDispatchSubscriber.getInstance();
|
|
936
970
|
const delivered = await subscriber.redispatch(wi);
|
|
971
|
+
if (delivered)
|
|
972
|
+
this.lastRedeliverAt.set(action.workItemId, Date.now());
|
|
937
973
|
if (delivered) {
|
|
938
974
|
this.logger.info('Redelivered WorkItem brief to active-but-idle agent', {
|
|
939
975
|
agent: agentSessionName,
|