crewly 1.20.361 → 1.20.362
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/team-leader/prompt.md +1 -1
- package/config/roles/team-leader/tl-addon.md +1 -1
- package/config/skills/team-leader/delegate-task/SKILL.md +21 -6
- package/config/skills/team-leader/delegate-task/execute.sh +12 -1
- package/dist/backend/backend/src/constants.d.ts +153 -6
- package/dist/backend/backend/src/constants.d.ts.map +1 -1
- package/dist/backend/backend/src/constants.js +153 -6
- package/dist/backend/backend/src/constants.js.map +1 -1
- package/dist/backend/backend/src/controllers/task-pool/task-pool.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/task-pool/task-pool.controller.js +7 -4
- package/dist/backend/backend/src/controllers/task-pool/task-pool.controller.js.map +1 -1
- package/dist/backend/backend/src/index.d.ts.map +1 -1
- package/dist/backend/backend/src/index.js +53 -1
- package/dist/backend/backend/src/index.js.map +1 -1
- package/dist/backend/backend/src/services/agent/follow-through.wiring.d.ts.map +1 -1
- package/dist/backend/backend/src/services/agent/follow-through.wiring.js +2 -0
- package/dist/backend/backend/src/services/agent/follow-through.wiring.js.map +1 -1
- package/dist/backend/backend/src/services/agent/handover-interrupted.d.ts +76 -0
- package/dist/backend/backend/src/services/agent/handover-interrupted.d.ts.map +1 -0
- package/dist/backend/backend/src/services/agent/handover-interrupted.js +139 -0
- package/dist/backend/backend/src/services/agent/handover-interrupted.js.map +1 -0
- package/dist/backend/backend/src/services/agent/handover-pointers.d.ts +4 -0
- package/dist/backend/backend/src/services/agent/handover-pointers.d.ts.map +1 -1
- package/dist/backend/backend/src/services/agent/handover-pointers.js +11 -4
- package/dist/backend/backend/src/services/agent/handover-pointers.js.map +1 -1
- package/dist/backend/backend/src/services/agent/idle-work-guard.d.ts +3 -1
- package/dist/backend/backend/src/services/agent/idle-work-guard.d.ts.map +1 -1
- package/dist/backend/backend/src/services/agent/idle-work-guard.js +2 -0
- package/dist/backend/backend/src/services/agent/idle-work-guard.js.map +1 -1
- package/dist/backend/backend/src/services/agent/resource-mode-protection.d.ts +5 -1
- package/dist/backend/backend/src/services/agent/resource-mode-protection.d.ts.map +1 -1
- package/dist/backend/backend/src/services/agent/resource-mode-protection.js +6 -1
- package/dist/backend/backend/src/services/agent/resource-mode-protection.js.map +1 -1
- package/dist/backend/backend/src/services/agent/runtime-session-recovery.d.ts +7 -0
- package/dist/backend/backend/src/services/agent/runtime-session-recovery.d.ts.map +1 -1
- package/dist/backend/backend/src/services/agent/runtime-session-recovery.js +18 -1
- package/dist/backend/backend/src/services/agent/runtime-session-recovery.js.map +1 -1
- package/dist/backend/backend/src/services/chat-v2/chat-v2.singleton.d.ts +8 -0
- package/dist/backend/backend/src/services/chat-v2/chat-v2.singleton.d.ts.map +1 -1
- package/dist/backend/backend/src/services/chat-v2/chat-v2.singleton.js +10 -0
- package/dist/backend/backend/src/services/chat-v2/chat-v2.singleton.js.map +1 -1
- package/dist/backend/backend/src/services/open-items/open-item-extractor.d.ts +43 -2
- package/dist/backend/backend/src/services/open-items/open-item-extractor.d.ts.map +1 -1
- package/dist/backend/backend/src/services/open-items/open-item-extractor.js +124 -13
- package/dist/backend/backend/src/services/open-items/open-item-extractor.js.map +1 -1
- package/dist/backend/backend/src/services/open-items/open-items.service.d.ts +40 -1
- package/dist/backend/backend/src/services/open-items/open-items.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/open-items/open-items.service.js +76 -3
- package/dist/backend/backend/src/services/open-items/open-items.service.js.map +1 -1
- package/dist/backend/backend/src/services/open-items/open-items.wiring.d.ts +8 -0
- package/dist/backend/backend/src/services/open-items/open-items.wiring.d.ts.map +1 -1
- package/dist/backend/backend/src/services/open-items/open-items.wiring.js +19 -1
- package/dist/backend/backend/src/services/open-items/open-items.wiring.js.map +1 -1
- package/dist/backend/backend/src/services/orc/owner-completion-report.service.d.ts +35 -19
- package/dist/backend/backend/src/services/orc/owner-completion-report.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/orc/owner-completion-report.service.js +60 -63
- package/dist/backend/backend/src/services/orc/owner-completion-report.service.js.map +1 -1
- package/dist/backend/backend/src/services/orc/owner-completion-report.text.d.ts +86 -0
- package/dist/backend/backend/src/services/orc/owner-completion-report.text.d.ts.map +1 -0
- package/dist/backend/backend/src/services/orc/owner-completion-report.text.js +170 -0
- package/dist/backend/backend/src/services/orc/owner-completion-report.text.js.map +1 -0
- package/dist/backend/backend/src/services/orc/owner-completion-report.wiring.d.ts +24 -0
- package/dist/backend/backend/src/services/orc/owner-completion-report.wiring.d.ts.map +1 -1
- package/dist/backend/backend/src/services/orc/owner-completion-report.wiring.js +38 -1
- package/dist/backend/backend/src/services/orc/owner-completion-report.wiring.js.map +1 -1
- package/dist/backend/backend/src/services/orc/reply-destination.wiring.d.ts +2 -0
- package/dist/backend/backend/src/services/orc/reply-destination.wiring.d.ts.map +1 -1
- package/dist/backend/backend/src/services/orc/reply-destination.wiring.js +8 -3
- package/dist/backend/backend/src/services/orc/reply-destination.wiring.js.map +1 -1
- package/dist/backend/backend/src/services/orc/work-item-destination.d.ts +76 -0
- package/dist/backend/backend/src/services/orc/work-item-destination.d.ts.map +1 -1
- package/dist/backend/backend/src/services/orc/work-item-destination.js +83 -3
- package/dist/backend/backend/src/services/orc/work-item-destination.js.map +1 -1
- package/dist/backend/backend/src/services/orc/work-item-destination.wiring.d.ts +31 -2
- package/dist/backend/backend/src/services/orc/work-item-destination.wiring.d.ts.map +1 -1
- package/dist/backend/backend/src/services/orc/work-item-destination.wiring.js +38 -0
- package/dist/backend/backend/src/services/orc/work-item-destination.wiring.js.map +1 -1
- package/dist/backend/backend/src/services/stall/background-run-detector.d.ts +56 -0
- package/dist/backend/backend/src/services/stall/background-run-detector.d.ts.map +1 -0
- package/dist/backend/backend/src/services/stall/background-run-detector.js +93 -0
- package/dist/backend/backend/src/services/stall/background-run-detector.js.map +1 -0
- package/dist/backend/backend/src/services/stall/claim-check.d.ts +60 -0
- package/dist/backend/backend/src/services/stall/claim-check.d.ts.map +1 -0
- package/dist/backend/backend/src/services/stall/claim-check.js +98 -0
- package/dist/backend/backend/src/services/stall/claim-check.js.map +1 -0
- package/dist/backend/backend/src/services/stall/in-flight-runs.store.d.ts +110 -0
- package/dist/backend/backend/src/services/stall/in-flight-runs.store.d.ts.map +1 -0
- package/dist/backend/backend/src/services/stall/in-flight-runs.store.js +109 -0
- package/dist/backend/backend/src/services/stall/in-flight-runs.store.js.map +1 -0
- package/dist/backend/backend/src/services/stall/stall-watcher.service.d.ts +456 -0
- package/dist/backend/backend/src/services/stall/stall-watcher.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/stall/stall-watcher.service.js +854 -0
- package/dist/backend/backend/src/services/stall/stall-watcher.service.js.map +1 -0
- package/dist/backend/backend/src/services/stall/stall-watcher.wiring.d.ts +98 -0
- package/dist/backend/backend/src/services/stall/stall-watcher.wiring.d.ts.map +1 -0
- package/dist/backend/backend/src/services/stall/stall-watcher.wiring.js +217 -0
- package/dist/backend/backend/src/services/stall/stall-watcher.wiring.js.map +1 -0
- package/dist/backend/backend/src/services/task-pool/task-pool.service.d.ts +53 -0
- package/dist/backend/backend/src/services/task-pool/task-pool.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/task-pool/task-pool.service.js +162 -15
- package/dist/backend/backend/src/services/task-pool/task-pool.service.js.map +1 -1
- package/dist/backend/backend/src/services/tl-delegation/tl-delegation.service.js +1 -1
- package/dist/backend/backend/src/services/tl-delegation/tl-delegation.service.js.map +1 -1
- package/dist/backend/backend/src/types/v2/open-item.types.d.ts +2 -0
- package/dist/backend/backend/src/types/v2/open-item.types.d.ts.map +1 -1
- package/dist/backend/build-info.json +2 -2
- package/dist/cli/backend/src/constants.d.ts +153 -6
- package/dist/cli/backend/src/constants.d.ts.map +1 -1
- package/dist/cli/backend/src/constants.js +153 -6
- package/dist/cli/backend/src/constants.js.map +1 -1
- package/dist/cli/backend/src/services/agent/handover-interrupted.d.ts +76 -0
- package/dist/cli/backend/src/services/agent/handover-interrupted.d.ts.map +1 -0
- package/dist/cli/backend/src/services/agent/handover-interrupted.js +139 -0
- package/dist/cli/backend/src/services/agent/handover-interrupted.js.map +1 -0
- package/dist/cli/backend/src/services/agent/handover-pointers.d.ts +4 -0
- package/dist/cli/backend/src/services/agent/handover-pointers.d.ts.map +1 -1
- package/dist/cli/backend/src/services/agent/handover-pointers.js +11 -4
- package/dist/cli/backend/src/services/agent/handover-pointers.js.map +1 -1
- package/dist/cli/backend/src/services/agent/runtime-session-recovery.d.ts +7 -0
- package/dist/cli/backend/src/services/agent/runtime-session-recovery.d.ts.map +1 -1
- package/dist/cli/backend/src/services/agent/runtime-session-recovery.js +18 -1
- package/dist/cli/backend/src/services/agent/runtime-session-recovery.js.map +1 -1
- package/dist/cli/backend/src/services/chat-v2/chat-v2.singleton.d.ts +8 -0
- package/dist/cli/backend/src/services/chat-v2/chat-v2.singleton.d.ts.map +1 -1
- package/dist/cli/backend/src/services/chat-v2/chat-v2.singleton.js +10 -0
- package/dist/cli/backend/src/services/chat-v2/chat-v2.singleton.js.map +1 -1
- package/dist/cli/backend/src/services/open-items/open-item-extractor.d.ts +43 -2
- package/dist/cli/backend/src/services/open-items/open-item-extractor.d.ts.map +1 -1
- package/dist/cli/backend/src/services/open-items/open-item-extractor.js +124 -13
- package/dist/cli/backend/src/services/open-items/open-item-extractor.js.map +1 -1
- package/dist/cli/backend/src/services/open-items/open-items.service.d.ts +40 -1
- package/dist/cli/backend/src/services/open-items/open-items.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/open-items/open-items.service.js +76 -3
- package/dist/cli/backend/src/services/open-items/open-items.service.js.map +1 -1
- package/dist/cli/backend/src/services/orc/work-item-destination.d.ts +76 -0
- package/dist/cli/backend/src/services/orc/work-item-destination.d.ts.map +1 -1
- package/dist/cli/backend/src/services/orc/work-item-destination.js +83 -3
- package/dist/cli/backend/src/services/orc/work-item-destination.js.map +1 -1
- package/dist/cli/backend/src/services/task-pool/task-pool.service.d.ts +53 -0
- package/dist/cli/backend/src/services/task-pool/task-pool.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/task-pool/task-pool.service.js +162 -15
- package/dist/cli/backend/src/services/task-pool/task-pool.service.js.map +1 -1
- package/dist/cli/backend/src/types/v2/open-item.types.d.ts +2 -0
- package/dist/cli/backend/src/types/v2/open-item.types.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Text heuristics for background runs (crewly#1363 kind c): an agent telling
|
|
3
|
+
* the owner or a room that it started something that runs on its own ("已经
|
|
4
|
+
* 开跑…预计 1 小时左右", "kicked off the render, about 40 min"), a later post
|
|
5
|
+
* that reports it finished or failed, and a post that carries a deliverable
|
|
6
|
+
* (a file or a link). No LLM. The duration comes from the open-items parser
|
|
7
|
+
* so the two agree on "一个半小时".
|
|
8
|
+
*
|
|
9
|
+
* @module services/stall/background-run-detector
|
|
10
|
+
*/
|
|
11
|
+
import { STALL_WATCHER_CONSTANTS } from '../../constants.js';
|
|
12
|
+
import { durationMs, splitSentences } from '../open-items/open-item-extractor.js';
|
|
13
|
+
const C = STALL_WATCHER_CONSTANTS;
|
|
14
|
+
/** A sentence that asks something is not an announcement. */
|
|
15
|
+
const QUESTION_END = /[??]\s*[))]?$/;
|
|
16
|
+
/**
|
|
17
|
+
* Clip a sentence to one line of at most RUN_HEAD_MAX_CHARS.
|
|
18
|
+
*
|
|
19
|
+
* @param s - Sentence
|
|
20
|
+
* @returns Clipped text
|
|
21
|
+
*/
|
|
22
|
+
function clipHead(s) {
|
|
23
|
+
const one = s.replace(/\s+/g, ' ').trim();
|
|
24
|
+
return one.length > C.RUN_HEAD_MAX_CHARS ? `${one.slice(0, C.RUN_HEAD_MAX_CHARS - 1)}…` : one;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Whether an agent post announces a run that will take a while.
|
|
28
|
+
*
|
|
29
|
+
* The announcing sentence must match {@link STALL_WATCHER_CONSTANTS.BACKGROUND_RUN_PATTERN};
|
|
30
|
+
* the duration is read from that sentence, else from the sentence before or
|
|
31
|
+
* after it ("已经开跑。预计 1 小时左右。"). Without a duration anywhere near, the
|
|
32
|
+
* post is NOT a background run (the caller registers nothing): the watcher
|
|
33
|
+
* only keys to the agent's own estimate. Finished-work reports and questions
|
|
34
|
+
* return null.
|
|
35
|
+
*
|
|
36
|
+
* @param text - The agent's post
|
|
37
|
+
* @returns The run, or null
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```typescript
|
|
41
|
+
* detectBackgroundRun('已经开跑了,预计 1 小时左右出结果。');
|
|
42
|
+
* // { head: '已经开跑了,预计 1 小时左右出结果。', estimateMs: 3600000 }
|
|
43
|
+
* detectBackgroundRun('Kicked off the render.'); // null — no duration
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
export function detectBackgroundRun(text) {
|
|
47
|
+
const t = (text ?? '').trim();
|
|
48
|
+
if (t.length < 4)
|
|
49
|
+
return null;
|
|
50
|
+
const sentences = splitSentences(t).map((s) => s.text.trim()).filter(Boolean);
|
|
51
|
+
for (let i = 0; i < sentences.length; i++) {
|
|
52
|
+
const s = sentences[i];
|
|
53
|
+
if (QUESTION_END.test(s) || !C.BACKGROUND_RUN_PATTERN.test(s))
|
|
54
|
+
continue;
|
|
55
|
+
if (isRunFinishedReport(s))
|
|
56
|
+
continue;
|
|
57
|
+
const near = [s, sentences[i + 1], sentences[i - 1]].filter((x) => !!x);
|
|
58
|
+
let estimateMs = null;
|
|
59
|
+
for (const n of near) {
|
|
60
|
+
estimateMs = durationMs(n);
|
|
61
|
+
if (estimateMs !== null)
|
|
62
|
+
break;
|
|
63
|
+
}
|
|
64
|
+
if (estimateMs === null)
|
|
65
|
+
return null;
|
|
66
|
+
return { head: clipHead(s), estimateMs };
|
|
67
|
+
}
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Whether a post reports a run finished or failed (either closes the record).
|
|
72
|
+
*
|
|
73
|
+
* @param text - The agent's post
|
|
74
|
+
* @returns True for a finished / failed report
|
|
75
|
+
*/
|
|
76
|
+
export function isRunFinishedReport(text) {
|
|
77
|
+
const t = (text ?? '').trim();
|
|
78
|
+
if (!t || QUESTION_END.test(t))
|
|
79
|
+
return false;
|
|
80
|
+
return C.RUN_FINISHED_PATTERN.test(t);
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Whether a post carries a deliverable: an attachment, a link or a file name.
|
|
84
|
+
*
|
|
85
|
+
* @param message - Content and attachment count
|
|
86
|
+
* @returns True when something was delivered
|
|
87
|
+
*/
|
|
88
|
+
export function hasDeliverable(message) {
|
|
89
|
+
if ((message.attachmentCount ?? 0) > 0)
|
|
90
|
+
return true;
|
|
91
|
+
return C.DELIVERABLE_PATTERN.test(message.content ?? '');
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=background-run-detector.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"background-run-detector.js","sourceRoot":"","sources":["../../../../../../backend/src/services/stall/background-run-detector.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,uBAAuB,EAAE,MAAM,oBAAoB,CAAC;AAC7D,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,sCAAsC,CAAC;AAElF,MAAM,CAAC,GAAG,uBAAuB,CAAC;AAUlC,6DAA6D;AAC7D,MAAM,YAAY,GAAG,eAAe,CAAC;AAErC;;;;;GAKG;AACH,SAAS,QAAQ,CAAC,CAAS;IAC1B,MAAM,GAAG,GAAG,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;IAC1C,OAAO,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,kBAAkB,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC;AAC/F,CAAC;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,UAAU,mBAAmB,CAAC,IAAY;IAC/C,MAAM,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IAC9B,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IAC9B,MAAM,SAAS,GAAG,cAAc,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAC9E,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,SAAS,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QAC3C,MAAM,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC;QACvB,IAAI,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,sBAAsB,CAAC,IAAI,CAAC,CAAC,CAAC;YAAE,SAAS;QACxE,IAAI,mBAAmB,CAAC,CAAC,CAAC;YAAE,SAAS;QACrC,MAAM,IAAI,GAAG,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAe,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACrF,IAAI,UAAU,GAAkB,IAAI,CAAC;QACrC,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;YACtB,UAAU,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC;YAC3B,IAAI,UAAU,KAAK,IAAI;gBAAE,MAAM;QAChC,CAAC;QACD,IAAI,UAAU,KAAK,IAAI;YAAE,OAAO,IAAI,CAAC;QACrC,OAAO,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC;IAC1C,CAAC;IACD,OAAO,IAAI,CAAC;AACb,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,mBAAmB,CAAC,IAAY;IAC/C,MAAM,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IAC9B,IAAI,CAAC,CAAC,IAAI,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC;QAAE,OAAO,KAAK,CAAC;IAC7C,OAAO,CAAC,CAAC,oBAAoB,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AACvC,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,cAAc,CAAC,OAAsD;IACpF,IAAI,CAAC,OAAO,CAAC,eAAe,IAAI,CAAC,CAAC,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IACpD,OAAO,CAAC,CAAC,mBAAmB,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC;AAC1D,CAAC"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* "X is doing it" claims (crewly#1362): an agent tells the owner a named
|
|
3
|
+
* teammate is doing something. The watcher later checks that a WorkItem or
|
|
4
|
+
* project ticket for that teammate exists around that time; when none does,
|
|
5
|
+
* the speaker is told once to delegate or correct the thread. This module is
|
|
6
|
+
* the text side: finding the claims and wording the nudge. No LLM.
|
|
7
|
+
*
|
|
8
|
+
* @module services/stall/claim-check
|
|
9
|
+
*/
|
|
10
|
+
/** A teammate the agent can name. */
|
|
11
|
+
export interface ClaimableMember {
|
|
12
|
+
/** Display name as teammates write it ("Pia") */
|
|
13
|
+
name: string;
|
|
14
|
+
/** Agent session */
|
|
15
|
+
session: string;
|
|
16
|
+
}
|
|
17
|
+
/** One claim found in a post. */
|
|
18
|
+
export interface TeammateClaim {
|
|
19
|
+
/** The teammate's session */
|
|
20
|
+
member: string;
|
|
21
|
+
/** The name as matched */
|
|
22
|
+
memberName: string;
|
|
23
|
+
/** The claiming sentence (clipped to RUN_HEAD_MAX_CHARS) */
|
|
24
|
+
sentence: string;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* The claim patterns for one name.
|
|
28
|
+
*
|
|
29
|
+
* @param name - Display name
|
|
30
|
+
* @returns Patterns that match a claim about that teammate
|
|
31
|
+
*/
|
|
32
|
+
export declare function claimPatternsFor(name: string): RegExp[];
|
|
33
|
+
/**
|
|
34
|
+
* Claims in an agent's post that a named teammate is doing the work.
|
|
35
|
+
*
|
|
36
|
+
* A member is matched by its display name (not the speaker itself); each
|
|
37
|
+
* member is reported once, with the first sentence that claims it. Questions
|
|
38
|
+
* and negated sentences ("不是 Pia 在做") are skipped.
|
|
39
|
+
*
|
|
40
|
+
* @param text - The agent's post
|
|
41
|
+
* @param members - The speaker's teammates (name + session)
|
|
42
|
+
* @param speaker - The speaker's session (never a claim about itself)
|
|
43
|
+
* @returns Claims found, in sentence order
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* ```typescript
|
|
47
|
+
* detectTeammateClaims('对,视频已经是 Pia 在做了。', [{ name: 'Pia', session: 'flopost-pia' }], 'flopost-ella');
|
|
48
|
+
* // [{ member: 'flopost-pia', memberName: 'Pia', sentence: '对,视频已经是 Pia 在做了。' }]
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
export declare function detectTeammateClaims(text: string, members: readonly ClaimableMember[], speaker: string): TeammateClaim[];
|
|
52
|
+
/**
|
|
53
|
+
* The one line the speaker gets when nothing is assigned to the teammate.
|
|
54
|
+
*
|
|
55
|
+
* @param memberName - The teammate as named
|
|
56
|
+
* @param sentence - The claiming sentence
|
|
57
|
+
* @returns Nudge text
|
|
58
|
+
*/
|
|
59
|
+
export declare function claimNudgeText(memberName: string, sentence: string): string;
|
|
60
|
+
//# sourceMappingURL=claim-check.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claim-check.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/stall/claim-check.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAOH,qCAAqC;AACrC,MAAM,WAAW,eAAe;IAC/B,iDAAiD;IACjD,IAAI,EAAE,MAAM,CAAC;IACb,oBAAoB;IACpB,OAAO,EAAE,MAAM,CAAC;CAChB;AAED,iCAAiC;AACjC,MAAM,WAAW,aAAa;IAC7B,6BAA6B;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,0BAA0B;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,4DAA4D;IAC5D,QAAQ,EAAE,MAAM,CAAC;CACjB;AAkBD;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAYvD;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,eAAe,EAAE,EAAE,OAAO,EAAE,MAAM,GAAG,aAAa,EAAE,CAmBxH;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAK3E"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* "X is doing it" claims (crewly#1362): an agent tells the owner a named
|
|
3
|
+
* teammate is doing something. The watcher later checks that a WorkItem or
|
|
4
|
+
* project ticket for that teammate exists around that time; when none does,
|
|
5
|
+
* the speaker is told once to delegate or correct the thread. This module is
|
|
6
|
+
* the text side: finding the claims and wording the nudge. No LLM.
|
|
7
|
+
*
|
|
8
|
+
* @module services/stall/claim-check
|
|
9
|
+
*/
|
|
10
|
+
import { STALL_WATCHER_CONSTANTS } from '../../constants.js';
|
|
11
|
+
import { splitSentences } from '../open-items/open-item-extractor.js';
|
|
12
|
+
const C = STALL_WATCHER_CONSTANTS;
|
|
13
|
+
/** A sentence that asks is not a claim ("让 Pia 做?"). */
|
|
14
|
+
const QUESTION_END = /[??]\s*[))]?$/;
|
|
15
|
+
/** A sentence that denies or undoes the claim. */
|
|
16
|
+
const NEGATED = /(?:不是|没有?让|没有?交给|并不是|不再|不用|别让)|\b(?:not|isn'?t|wasn'?t|haven'?t|hasn'?t|no longer|didn'?t)\b/iu;
|
|
17
|
+
/**
|
|
18
|
+
* Escape a name for use in a RegExp.
|
|
19
|
+
*
|
|
20
|
+
* @param name - Display name
|
|
21
|
+
* @returns Escaped text
|
|
22
|
+
*/
|
|
23
|
+
function escapeName(name) {
|
|
24
|
+
return name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* The claim patterns for one name.
|
|
28
|
+
*
|
|
29
|
+
* @param name - Display name
|
|
30
|
+
* @returns Patterns that match a claim about that teammate
|
|
31
|
+
*/
|
|
32
|
+
export function claimPatternsFor(name) {
|
|
33
|
+
const n = escapeName(name);
|
|
34
|
+
// English names need word boundaries; CJK names have none, so the verb
|
|
35
|
+
// lists carry the shape on their own.
|
|
36
|
+
const latin = /^[\p{Script=Latin}\d\s.'-]+$/u.test(name);
|
|
37
|
+
const b = latin ? '\\b' : '';
|
|
38
|
+
return [
|
|
39
|
+
new RegExp(`${b}${n}${b}\\s*${C.CLAIM_ZH_AFTER_NAME}`, 'iu'),
|
|
40
|
+
new RegExp(`${C.CLAIM_ZH_BEFORE_NAME}\\s*${n}${b}`, 'iu'),
|
|
41
|
+
new RegExp(`${b}${n}${b}\\s*${C.CLAIM_EN_AFTER_NAME}\\b`, 'iu'),
|
|
42
|
+
new RegExp(`\\b${C.CLAIM_EN_BEFORE_NAME}\\s+${n}${b}`, 'iu'),
|
|
43
|
+
];
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Claims in an agent's post that a named teammate is doing the work.
|
|
47
|
+
*
|
|
48
|
+
* A member is matched by its display name (not the speaker itself); each
|
|
49
|
+
* member is reported once, with the first sentence that claims it. Questions
|
|
50
|
+
* and negated sentences ("不是 Pia 在做") are skipped.
|
|
51
|
+
*
|
|
52
|
+
* @param text - The agent's post
|
|
53
|
+
* @param members - The speaker's teammates (name + session)
|
|
54
|
+
* @param speaker - The speaker's session (never a claim about itself)
|
|
55
|
+
* @returns Claims found, in sentence order
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* ```typescript
|
|
59
|
+
* detectTeammateClaims('对,视频已经是 Pia 在做了。', [{ name: 'Pia', session: 'flopost-pia' }], 'flopost-ella');
|
|
60
|
+
* // [{ member: 'flopost-pia', memberName: 'Pia', sentence: '对,视频已经是 Pia 在做了。' }]
|
|
61
|
+
* ```
|
|
62
|
+
*/
|
|
63
|
+
export function detectTeammateClaims(text, members, speaker) {
|
|
64
|
+
const t = (text ?? '').trim();
|
|
65
|
+
if (t.length < 4 || members.length === 0)
|
|
66
|
+
return [];
|
|
67
|
+
const candidates = members.filter((m) => m.session !== speaker && m.name.trim().length > 0);
|
|
68
|
+
if (candidates.length === 0)
|
|
69
|
+
return [];
|
|
70
|
+
const patterns = candidates.map((m) => ({ m, res: claimPatternsFor(m.name.trim()) }));
|
|
71
|
+
const out = [];
|
|
72
|
+
const seen = new Set();
|
|
73
|
+
for (const { text: raw } of splitSentences(t)) {
|
|
74
|
+
const s = raw.trim();
|
|
75
|
+
if (!s || QUESTION_END.test(s) || NEGATED.test(s))
|
|
76
|
+
continue;
|
|
77
|
+
for (const { m, res } of patterns) {
|
|
78
|
+
if (seen.has(m.session) || !res.some((re) => re.test(s)))
|
|
79
|
+
continue;
|
|
80
|
+
seen.add(m.session);
|
|
81
|
+
const one = s.replace(/\s+/g, ' ');
|
|
82
|
+
out.push({ member: m.session, memberName: m.name.trim(), sentence: one.length > C.RUN_HEAD_MAX_CHARS ? `${one.slice(0, C.RUN_HEAD_MAX_CHARS - 1)}…` : one });
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return out;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* The one line the speaker gets when nothing is assigned to the teammate.
|
|
89
|
+
*
|
|
90
|
+
* @param memberName - The teammate as named
|
|
91
|
+
* @param sentence - The claiming sentence
|
|
92
|
+
* @returns Nudge text
|
|
93
|
+
*/
|
|
94
|
+
export function claimNudgeText(memberName, sentence) {
|
|
95
|
+
return (`${C.CLAIM_CHECK_TAG} You told the owner ${memberName} is doing it ("${sentence}"); nothing is assigned to ${memberName} ` +
|
|
96
|
+
`(no work item or ticket for ${memberName} within ${Math.round(C.CLAIM_CHECK_WINDOW_MS / 60_000)} minutes of that reply). Delegate it now, or correct the thread.`);
|
|
97
|
+
}
|
|
98
|
+
//# sourceMappingURL=claim-check.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claim-check.js","sourceRoot":"","sources":["../../../../../../backend/src/services/stall/claim-check.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,uBAAuB,EAAE,MAAM,oBAAoB,CAAC;AAC7D,OAAO,EAAE,cAAc,EAAE,MAAM,sCAAsC,CAAC;AAEtE,MAAM,CAAC,GAAG,uBAAuB,CAAC;AAoBlC,wDAAwD;AACxD,MAAM,YAAY,GAAG,eAAe,CAAC;AAErC,kDAAkD;AAClD,MAAM,OAAO,GAAG,gGAAgG,CAAC;AAEjH;;;;;GAKG;AACH,SAAS,UAAU,CAAC,IAAY;IAC/B,OAAO,IAAI,CAAC,OAAO,CAAC,qBAAqB,EAAE,MAAM,CAAC,CAAC;AACpD,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAAC,IAAY;IAC5C,MAAM,CAAC,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC;IAC3B,uEAAuE;IACvE,sCAAsC;IACtC,MAAM,KAAK,GAAG,+BAA+B,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACzD,MAAM,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;IAC7B,OAAO;QACN,IAAI,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,mBAAmB,EAAE,EAAE,IAAI,CAAC;QAC5D,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,oBAAoB,OAAO,CAAC,GAAG,CAAC,EAAE,EAAE,IAAI,CAAC;QACzD,IAAI,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,mBAAmB,KAAK,EAAE,IAAI,CAAC;QAC/D,IAAI,MAAM,CAAC,MAAM,CAAC,CAAC,oBAAoB,OAAO,CAAC,GAAG,CAAC,EAAE,EAAE,IAAI,CAAC;KAC5D,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,oBAAoB,CAAC,IAAY,EAAE,OAAmC,EAAE,OAAe;IACtG,MAAM,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IAC9B,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IACpD,MAAM,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,KAAK,OAAO,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAC5F,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IACvC,MAAM,QAAQ,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,EAAE,gBAAgB,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;IACtF,MAAM,GAAG,GAAoB,EAAE,CAAC;IAChC,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,KAAK,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,cAAc,CAAC,CAAC,CAAC,EAAE,CAAC;QAC/C,MAAM,CAAC,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;QACrB,IAAI,CAAC,CAAC,IAAI,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;YAAE,SAAS;QAC5D,KAAK,MAAM,EAAE,CAAC,EAAE,GAAG,EAAE,IAAI,QAAQ,EAAE,CAAC;YACnC,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;gBAAE,SAAS;YACnE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;YACpB,MAAM,GAAG,GAAG,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;YACnC,GAAG,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,OAAO,EAAE,UAAU,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,QAAQ,EAAE,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,kBAAkB,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC;QAC9J,CAAC;IACF,CAAC;IACD,OAAO,GAAG,CAAC;AACZ,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,cAAc,CAAC,UAAkB,EAAE,QAAgB;IAClE,OAAO,CACN,GAAG,CAAC,CAAC,eAAe,uBAAuB,UAAU,kBAAkB,QAAQ,8BAA8B,UAAU,GAAG;QAC1H,+BAA+B,UAAU,WAAW,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,qBAAqB,GAAG,MAAM,CAAC,kEAAkE,CAClK,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The stall watcher's state on disk (`CREWLY_HOME/in-flight-runs.json`):
|
|
3
|
+
* background runs agents said they started, the ladder stamps of every
|
|
4
|
+
* stalled item (agent / lead / owner steps), pending teammate-claim checks,
|
|
5
|
+
* the per-agent nudge times and the time the watcher was first armed.
|
|
6
|
+
* One small JSON file, written atomically; a missing or broken file is an
|
|
7
|
+
* empty state (specs/2026-10-17-stall-watcher.md).
|
|
8
|
+
*
|
|
9
|
+
* @module services/stall/in-flight-runs.store
|
|
10
|
+
*/
|
|
11
|
+
/** A run an agent said it started, with when it should be back. */
|
|
12
|
+
export interface InFlightRun {
|
|
13
|
+
/** Stable id (the message id that announced it) */
|
|
14
|
+
id: string;
|
|
15
|
+
/** Agent session */
|
|
16
|
+
agent: string;
|
|
17
|
+
/** chat-v2 channel of the announcement */
|
|
18
|
+
channelId: string;
|
|
19
|
+
/** chat-v2 thread root (the announcement itself when it was a root post) */
|
|
20
|
+
threadId: string;
|
|
21
|
+
/** The sentence that announced it (clipped) */
|
|
22
|
+
head: string;
|
|
23
|
+
/** Epoch ms of the announcement */
|
|
24
|
+
startedAt: number;
|
|
25
|
+
/** Estimate the agent gave (ms), or the default */
|
|
26
|
+
estimateMs: number;
|
|
27
|
+
/** Whether the estimate came from the text */
|
|
28
|
+
estimateSource: 'text' | 'default';
|
|
29
|
+
/** startedAt + estimateMs */
|
|
30
|
+
expectedBy: number;
|
|
31
|
+
}
|
|
32
|
+
/** When each step of the ladder fired for one item (epoch ms). */
|
|
33
|
+
export interface LadderState {
|
|
34
|
+
agentAt?: number;
|
|
35
|
+
leadAt?: number;
|
|
36
|
+
ownerAt?: number;
|
|
37
|
+
/** The lead that was told at the lead step (its answer in the thread stops the owner step) */
|
|
38
|
+
leadSession?: string;
|
|
39
|
+
/** Last time any step of this item ran (for pruning) */
|
|
40
|
+
seenAt: number;
|
|
41
|
+
}
|
|
42
|
+
/** A "<Name> is doing it" told to the owner, waiting for its check (crewly#1362). */
|
|
43
|
+
export interface PendingClaim {
|
|
44
|
+
/** The message id that made the claim */
|
|
45
|
+
id: string;
|
|
46
|
+
/** Who said it */
|
|
47
|
+
speaker: string;
|
|
48
|
+
/** The teammate named (session) */
|
|
49
|
+
member: string;
|
|
50
|
+
/** The teammate's display name as written */
|
|
51
|
+
memberName: string;
|
|
52
|
+
/** Epoch ms of the claim */
|
|
53
|
+
saidAt: number;
|
|
54
|
+
/** The sentence (clipped) */
|
|
55
|
+
head: string;
|
|
56
|
+
}
|
|
57
|
+
/** Everything the watcher persists. */
|
|
58
|
+
export interface StallWatcherState {
|
|
59
|
+
version: number;
|
|
60
|
+
/** Epoch ms of the first real pass; sends start ARM_DELAY_MS later */
|
|
61
|
+
armedAt?: number;
|
|
62
|
+
runs: InFlightRun[];
|
|
63
|
+
/** Ladder stamps by item key (`promise:<request>/<item>`, `claim:<workItem>`, `run:<id>`) */
|
|
64
|
+
ladders: Record<string, LadderState>;
|
|
65
|
+
/** Last watcher nudge per agent session (epoch ms) */
|
|
66
|
+
lastNudgeByAgent: Record<string, number>;
|
|
67
|
+
claims: PendingClaim[];
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* A fresh, empty state.
|
|
71
|
+
*
|
|
72
|
+
* @returns Empty state
|
|
73
|
+
*/
|
|
74
|
+
export declare function emptyState(): StallWatcherState;
|
|
75
|
+
/**
|
|
76
|
+
* Drop what is too old to matter: runs past RUN_MAX_AGE_MS, ladders not
|
|
77
|
+
* seen for LADDER_TTL_MS, nudge times older than the cooldown, and claim
|
|
78
|
+
* checks past their window twice over (something kept them from running).
|
|
79
|
+
* Caps the run and claim lists at their maxima (oldest first).
|
|
80
|
+
*
|
|
81
|
+
* @param state - State (mutated in place)
|
|
82
|
+
* @param now - Epoch ms
|
|
83
|
+
* @returns The same state
|
|
84
|
+
*/
|
|
85
|
+
export declare function pruneState(state: StallWatcherState, now: number): StallWatcherState;
|
|
86
|
+
/**
|
|
87
|
+
* Reads and writes the state file.
|
|
88
|
+
*/
|
|
89
|
+
export declare class InFlightRunsStore {
|
|
90
|
+
private readonly filePath;
|
|
91
|
+
/**
|
|
92
|
+
* @param filePath - Absolute path of the state file
|
|
93
|
+
*/
|
|
94
|
+
constructor(filePath: string);
|
|
95
|
+
/** @returns The state file path */
|
|
96
|
+
get path(): string;
|
|
97
|
+
/**
|
|
98
|
+
* Read the state; a missing, unreadable or malformed file is an empty state.
|
|
99
|
+
*
|
|
100
|
+
* @returns The state (missing lists filled in)
|
|
101
|
+
*/
|
|
102
|
+
load(): Promise<StallWatcherState>;
|
|
103
|
+
/**
|
|
104
|
+
* Write the state atomically (temp file + rename).
|
|
105
|
+
*
|
|
106
|
+
* @param state - State to write
|
|
107
|
+
*/
|
|
108
|
+
save(state: StallWatcherState): Promise<void>;
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=in-flight-runs.store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"in-flight-runs.store.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/stall/in-flight-runs.store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAQH,mEAAmE;AACnE,MAAM,WAAW,WAAW;IAC3B,mDAAmD;IACnD,EAAE,EAAE,MAAM,CAAC;IACX,oBAAoB;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,0CAA0C;IAC1C,SAAS,EAAE,MAAM,CAAC;IAClB,4EAA4E;IAC5E,QAAQ,EAAE,MAAM,CAAC;IACjB,+CAA+C;IAC/C,IAAI,EAAE,MAAM,CAAC;IACb,mCAAmC;IACnC,SAAS,EAAE,MAAM,CAAC;IAClB,mDAAmD;IACnD,UAAU,EAAE,MAAM,CAAC;IACnB,8CAA8C;IAC9C,cAAc,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,6BAA6B;IAC7B,UAAU,EAAE,MAAM,CAAC;CACnB;AAED,kEAAkE;AAClE,MAAM,WAAW,WAAW;IAC3B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,8FAA8F;IAC9F,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,wDAAwD;IACxD,MAAM,EAAE,MAAM,CAAC;CACf;AAED,qFAAqF;AACrF,MAAM,WAAW,YAAY;IAC5B,yCAAyC;IACzC,EAAE,EAAE,MAAM,CAAC;IACX,kBAAkB;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,mCAAmC;IACnC,MAAM,EAAE,MAAM,CAAC;IACf,6CAA6C;IAC7C,UAAU,EAAE,MAAM,CAAC;IACnB,4BAA4B;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,6BAA6B;IAC7B,IAAI,EAAE,MAAM,CAAC;CACb;AAED,uCAAuC;AACvC,MAAM,WAAW,iBAAiB;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,sEAAsE;IACtE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,WAAW,EAAE,CAAC;IACpB,6FAA6F;IAC7F,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IACrC,sDAAsD;IACtD,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzC,MAAM,EAAE,YAAY,EAAE,CAAC;CACvB;AAED;;;;GAIG;AACH,wBAAgB,UAAU,IAAI,iBAAiB,CAE9C;AAED;;;;;;;;;GASG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,iBAAiB,EAAE,GAAG,EAAE,MAAM,GAAG,iBAAiB,CAUnF;AAcD;;GAEG;AACH,qBAAa,iBAAiB;IAIjB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAHrC;;OAEG;gBAC0B,QAAQ,EAAE,MAAM;IAE7C,mCAAmC;IACnC,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED;;;;OAIG;IACG,IAAI,IAAI,OAAO,CAAC,iBAAiB,CAAC;IAkBxC;;;;OAIG;IACG,IAAI,CAAC,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;CAMnD"}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The stall watcher's state on disk (`CREWLY_HOME/in-flight-runs.json`):
|
|
3
|
+
* background runs agents said they started, the ladder stamps of every
|
|
4
|
+
* stalled item (agent / lead / owner steps), pending teammate-claim checks,
|
|
5
|
+
* the per-agent nudge times and the time the watcher was first armed.
|
|
6
|
+
* One small JSON file, written atomically; a missing or broken file is an
|
|
7
|
+
* empty state (specs/2026-10-17-stall-watcher.md).
|
|
8
|
+
*
|
|
9
|
+
* @module services/stall/in-flight-runs.store
|
|
10
|
+
*/
|
|
11
|
+
import { promises as fs } from 'fs';
|
|
12
|
+
import path from 'path';
|
|
13
|
+
import { STALL_WATCHER_CONSTANTS } from '../../constants.js';
|
|
14
|
+
const C = STALL_WATCHER_CONSTANTS;
|
|
15
|
+
/**
|
|
16
|
+
* A fresh, empty state.
|
|
17
|
+
*
|
|
18
|
+
* @returns Empty state
|
|
19
|
+
*/
|
|
20
|
+
export function emptyState() {
|
|
21
|
+
return { version: C.STATE_VERSION, runs: [], ladders: {}, lastNudgeByAgent: {}, claims: [] };
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Drop what is too old to matter: runs past RUN_MAX_AGE_MS, ladders not
|
|
25
|
+
* seen for LADDER_TTL_MS, nudge times older than the cooldown, and claim
|
|
26
|
+
* checks past their window twice over (something kept them from running).
|
|
27
|
+
* Caps the run and claim lists at their maxima (oldest first).
|
|
28
|
+
*
|
|
29
|
+
* @param state - State (mutated in place)
|
|
30
|
+
* @param now - Epoch ms
|
|
31
|
+
* @returns The same state
|
|
32
|
+
*/
|
|
33
|
+
export function pruneState(state, now) {
|
|
34
|
+
state.runs = state.runs.filter((r) => now - r.startedAt < C.RUN_MAX_AGE_MS).slice(-C.MAX_RUNS);
|
|
35
|
+
for (const [key, ladder] of Object.entries(state.ladders)) {
|
|
36
|
+
if (now - ladder.seenAt >= C.LADDER_TTL_MS)
|
|
37
|
+
delete state.ladders[key];
|
|
38
|
+
}
|
|
39
|
+
for (const [agent, at] of Object.entries(state.lastNudgeByAgent)) {
|
|
40
|
+
if (now - at >= C.NUDGE_COOLDOWN_MS)
|
|
41
|
+
delete state.lastNudgeByAgent[agent];
|
|
42
|
+
}
|
|
43
|
+
state.claims = state.claims.filter((c) => now - c.saidAt < 2 * C.CLAIM_CHECK_WINDOW_MS + C.INTERVAL_MS).slice(-C.MAX_PENDING_CLAIMS);
|
|
44
|
+
return state;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Whether a parsed file is a usable state.
|
|
48
|
+
*
|
|
49
|
+
* @param value - Parsed JSON
|
|
50
|
+
* @returns True for a state object
|
|
51
|
+
*/
|
|
52
|
+
function isState(value) {
|
|
53
|
+
if (!value || typeof value !== 'object')
|
|
54
|
+
return false;
|
|
55
|
+
const v = value;
|
|
56
|
+
return Array.isArray(v.runs) && typeof v.ladders === 'object' && v.ladders !== null;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Reads and writes the state file.
|
|
60
|
+
*/
|
|
61
|
+
export class InFlightRunsStore {
|
|
62
|
+
filePath;
|
|
63
|
+
/**
|
|
64
|
+
* @param filePath - Absolute path of the state file
|
|
65
|
+
*/
|
|
66
|
+
constructor(filePath) {
|
|
67
|
+
this.filePath = filePath;
|
|
68
|
+
}
|
|
69
|
+
/** @returns The state file path */
|
|
70
|
+
get path() {
|
|
71
|
+
return this.filePath;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Read the state; a missing, unreadable or malformed file is an empty state.
|
|
75
|
+
*
|
|
76
|
+
* @returns The state (missing lists filled in)
|
|
77
|
+
*/
|
|
78
|
+
async load() {
|
|
79
|
+
try {
|
|
80
|
+
const raw = await fs.readFile(this.filePath, 'utf8');
|
|
81
|
+
const parsed = JSON.parse(raw);
|
|
82
|
+
if (!isState(parsed))
|
|
83
|
+
return emptyState();
|
|
84
|
+
return {
|
|
85
|
+
...emptyState(),
|
|
86
|
+
...parsed,
|
|
87
|
+
runs: parsed.runs ?? [],
|
|
88
|
+
ladders: parsed.ladders ?? {},
|
|
89
|
+
lastNudgeByAgent: parsed.lastNudgeByAgent ?? {},
|
|
90
|
+
claims: parsed.claims ?? [],
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
return emptyState();
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Write the state atomically (temp file + rename).
|
|
99
|
+
*
|
|
100
|
+
* @param state - State to write
|
|
101
|
+
*/
|
|
102
|
+
async save(state) {
|
|
103
|
+
await fs.mkdir(path.dirname(this.filePath), { recursive: true });
|
|
104
|
+
const tmp = `${this.filePath}.${process.pid}.tmp`;
|
|
105
|
+
await fs.writeFile(tmp, JSON.stringify({ ...state, version: C.STATE_VERSION }, null, 2), 'utf8');
|
|
106
|
+
await fs.rename(tmp, this.filePath);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
//# sourceMappingURL=in-flight-runs.store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"in-flight-runs.store.js","sourceRoot":"","sources":["../../../../../../backend/src/services/stall/in-flight-runs.store.ts"],"names":[],"mappings":"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"}
|