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.
Files changed (145) hide show
  1. package/config/roles/team-leader/prompt.md +1 -1
  2. package/config/roles/team-leader/tl-addon.md +1 -1
  3. package/config/skills/team-leader/delegate-task/SKILL.md +21 -6
  4. package/config/skills/team-leader/delegate-task/execute.sh +12 -1
  5. package/dist/backend/backend/src/constants.d.ts +153 -6
  6. package/dist/backend/backend/src/constants.d.ts.map +1 -1
  7. package/dist/backend/backend/src/constants.js +153 -6
  8. package/dist/backend/backend/src/constants.js.map +1 -1
  9. package/dist/backend/backend/src/controllers/task-pool/task-pool.controller.d.ts.map +1 -1
  10. package/dist/backend/backend/src/controllers/task-pool/task-pool.controller.js +7 -4
  11. package/dist/backend/backend/src/controllers/task-pool/task-pool.controller.js.map +1 -1
  12. package/dist/backend/backend/src/index.d.ts.map +1 -1
  13. package/dist/backend/backend/src/index.js +53 -1
  14. package/dist/backend/backend/src/index.js.map +1 -1
  15. package/dist/backend/backend/src/services/agent/follow-through.wiring.d.ts.map +1 -1
  16. package/dist/backend/backend/src/services/agent/follow-through.wiring.js +2 -0
  17. package/dist/backend/backend/src/services/agent/follow-through.wiring.js.map +1 -1
  18. package/dist/backend/backend/src/services/agent/handover-interrupted.d.ts +76 -0
  19. package/dist/backend/backend/src/services/agent/handover-interrupted.d.ts.map +1 -0
  20. package/dist/backend/backend/src/services/agent/handover-interrupted.js +139 -0
  21. package/dist/backend/backend/src/services/agent/handover-interrupted.js.map +1 -0
  22. package/dist/backend/backend/src/services/agent/handover-pointers.d.ts +4 -0
  23. package/dist/backend/backend/src/services/agent/handover-pointers.d.ts.map +1 -1
  24. package/dist/backend/backend/src/services/agent/handover-pointers.js +11 -4
  25. package/dist/backend/backend/src/services/agent/handover-pointers.js.map +1 -1
  26. package/dist/backend/backend/src/services/agent/idle-work-guard.d.ts +3 -1
  27. package/dist/backend/backend/src/services/agent/idle-work-guard.d.ts.map +1 -1
  28. package/dist/backend/backend/src/services/agent/idle-work-guard.js +2 -0
  29. package/dist/backend/backend/src/services/agent/idle-work-guard.js.map +1 -1
  30. package/dist/backend/backend/src/services/agent/resource-mode-protection.d.ts +5 -1
  31. package/dist/backend/backend/src/services/agent/resource-mode-protection.d.ts.map +1 -1
  32. package/dist/backend/backend/src/services/agent/resource-mode-protection.js +6 -1
  33. package/dist/backend/backend/src/services/agent/resource-mode-protection.js.map +1 -1
  34. package/dist/backend/backend/src/services/agent/runtime-session-recovery.d.ts +7 -0
  35. package/dist/backend/backend/src/services/agent/runtime-session-recovery.d.ts.map +1 -1
  36. package/dist/backend/backend/src/services/agent/runtime-session-recovery.js +18 -1
  37. package/dist/backend/backend/src/services/agent/runtime-session-recovery.js.map +1 -1
  38. package/dist/backend/backend/src/services/chat-v2/chat-v2.singleton.d.ts +8 -0
  39. package/dist/backend/backend/src/services/chat-v2/chat-v2.singleton.d.ts.map +1 -1
  40. package/dist/backend/backend/src/services/chat-v2/chat-v2.singleton.js +10 -0
  41. package/dist/backend/backend/src/services/chat-v2/chat-v2.singleton.js.map +1 -1
  42. package/dist/backend/backend/src/services/open-items/open-item-extractor.d.ts +43 -2
  43. package/dist/backend/backend/src/services/open-items/open-item-extractor.d.ts.map +1 -1
  44. package/dist/backend/backend/src/services/open-items/open-item-extractor.js +124 -13
  45. package/dist/backend/backend/src/services/open-items/open-item-extractor.js.map +1 -1
  46. package/dist/backend/backend/src/services/open-items/open-items.service.d.ts +40 -1
  47. package/dist/backend/backend/src/services/open-items/open-items.service.d.ts.map +1 -1
  48. package/dist/backend/backend/src/services/open-items/open-items.service.js +76 -3
  49. package/dist/backend/backend/src/services/open-items/open-items.service.js.map +1 -1
  50. package/dist/backend/backend/src/services/open-items/open-items.wiring.d.ts +8 -0
  51. package/dist/backend/backend/src/services/open-items/open-items.wiring.d.ts.map +1 -1
  52. package/dist/backend/backend/src/services/open-items/open-items.wiring.js +19 -1
  53. package/dist/backend/backend/src/services/open-items/open-items.wiring.js.map +1 -1
  54. package/dist/backend/backend/src/services/orc/owner-completion-report.service.d.ts +35 -19
  55. package/dist/backend/backend/src/services/orc/owner-completion-report.service.d.ts.map +1 -1
  56. package/dist/backend/backend/src/services/orc/owner-completion-report.service.js +60 -63
  57. package/dist/backend/backend/src/services/orc/owner-completion-report.service.js.map +1 -1
  58. package/dist/backend/backend/src/services/orc/owner-completion-report.text.d.ts +86 -0
  59. package/dist/backend/backend/src/services/orc/owner-completion-report.text.d.ts.map +1 -0
  60. package/dist/backend/backend/src/services/orc/owner-completion-report.text.js +170 -0
  61. package/dist/backend/backend/src/services/orc/owner-completion-report.text.js.map +1 -0
  62. package/dist/backend/backend/src/services/orc/owner-completion-report.wiring.d.ts +24 -0
  63. package/dist/backend/backend/src/services/orc/owner-completion-report.wiring.d.ts.map +1 -1
  64. package/dist/backend/backend/src/services/orc/owner-completion-report.wiring.js +38 -1
  65. package/dist/backend/backend/src/services/orc/owner-completion-report.wiring.js.map +1 -1
  66. package/dist/backend/backend/src/services/orc/reply-destination.wiring.d.ts +2 -0
  67. package/dist/backend/backend/src/services/orc/reply-destination.wiring.d.ts.map +1 -1
  68. package/dist/backend/backend/src/services/orc/reply-destination.wiring.js +8 -3
  69. package/dist/backend/backend/src/services/orc/reply-destination.wiring.js.map +1 -1
  70. package/dist/backend/backend/src/services/orc/work-item-destination.d.ts +76 -0
  71. package/dist/backend/backend/src/services/orc/work-item-destination.d.ts.map +1 -1
  72. package/dist/backend/backend/src/services/orc/work-item-destination.js +83 -3
  73. package/dist/backend/backend/src/services/orc/work-item-destination.js.map +1 -1
  74. package/dist/backend/backend/src/services/orc/work-item-destination.wiring.d.ts +31 -2
  75. package/dist/backend/backend/src/services/orc/work-item-destination.wiring.d.ts.map +1 -1
  76. package/dist/backend/backend/src/services/orc/work-item-destination.wiring.js +38 -0
  77. package/dist/backend/backend/src/services/orc/work-item-destination.wiring.js.map +1 -1
  78. package/dist/backend/backend/src/services/stall/background-run-detector.d.ts +56 -0
  79. package/dist/backend/backend/src/services/stall/background-run-detector.d.ts.map +1 -0
  80. package/dist/backend/backend/src/services/stall/background-run-detector.js +93 -0
  81. package/dist/backend/backend/src/services/stall/background-run-detector.js.map +1 -0
  82. package/dist/backend/backend/src/services/stall/claim-check.d.ts +60 -0
  83. package/dist/backend/backend/src/services/stall/claim-check.d.ts.map +1 -0
  84. package/dist/backend/backend/src/services/stall/claim-check.js +98 -0
  85. package/dist/backend/backend/src/services/stall/claim-check.js.map +1 -0
  86. package/dist/backend/backend/src/services/stall/in-flight-runs.store.d.ts +110 -0
  87. package/dist/backend/backend/src/services/stall/in-flight-runs.store.d.ts.map +1 -0
  88. package/dist/backend/backend/src/services/stall/in-flight-runs.store.js +109 -0
  89. package/dist/backend/backend/src/services/stall/in-flight-runs.store.js.map +1 -0
  90. package/dist/backend/backend/src/services/stall/stall-watcher.service.d.ts +456 -0
  91. package/dist/backend/backend/src/services/stall/stall-watcher.service.d.ts.map +1 -0
  92. package/dist/backend/backend/src/services/stall/stall-watcher.service.js +854 -0
  93. package/dist/backend/backend/src/services/stall/stall-watcher.service.js.map +1 -0
  94. package/dist/backend/backend/src/services/stall/stall-watcher.wiring.d.ts +98 -0
  95. package/dist/backend/backend/src/services/stall/stall-watcher.wiring.d.ts.map +1 -0
  96. package/dist/backend/backend/src/services/stall/stall-watcher.wiring.js +217 -0
  97. package/dist/backend/backend/src/services/stall/stall-watcher.wiring.js.map +1 -0
  98. package/dist/backend/backend/src/services/task-pool/task-pool.service.d.ts +53 -0
  99. package/dist/backend/backend/src/services/task-pool/task-pool.service.d.ts.map +1 -1
  100. package/dist/backend/backend/src/services/task-pool/task-pool.service.js +162 -15
  101. package/dist/backend/backend/src/services/task-pool/task-pool.service.js.map +1 -1
  102. package/dist/backend/backend/src/services/tl-delegation/tl-delegation.service.js +1 -1
  103. package/dist/backend/backend/src/services/tl-delegation/tl-delegation.service.js.map +1 -1
  104. package/dist/backend/backend/src/types/v2/open-item.types.d.ts +2 -0
  105. package/dist/backend/backend/src/types/v2/open-item.types.d.ts.map +1 -1
  106. package/dist/backend/build-info.json +2 -2
  107. package/dist/cli/backend/src/constants.d.ts +153 -6
  108. package/dist/cli/backend/src/constants.d.ts.map +1 -1
  109. package/dist/cli/backend/src/constants.js +153 -6
  110. package/dist/cli/backend/src/constants.js.map +1 -1
  111. package/dist/cli/backend/src/services/agent/handover-interrupted.d.ts +76 -0
  112. package/dist/cli/backend/src/services/agent/handover-interrupted.d.ts.map +1 -0
  113. package/dist/cli/backend/src/services/agent/handover-interrupted.js +139 -0
  114. package/dist/cli/backend/src/services/agent/handover-interrupted.js.map +1 -0
  115. package/dist/cli/backend/src/services/agent/handover-pointers.d.ts +4 -0
  116. package/dist/cli/backend/src/services/agent/handover-pointers.d.ts.map +1 -1
  117. package/dist/cli/backend/src/services/agent/handover-pointers.js +11 -4
  118. package/dist/cli/backend/src/services/agent/handover-pointers.js.map +1 -1
  119. package/dist/cli/backend/src/services/agent/runtime-session-recovery.d.ts +7 -0
  120. package/dist/cli/backend/src/services/agent/runtime-session-recovery.d.ts.map +1 -1
  121. package/dist/cli/backend/src/services/agent/runtime-session-recovery.js +18 -1
  122. package/dist/cli/backend/src/services/agent/runtime-session-recovery.js.map +1 -1
  123. package/dist/cli/backend/src/services/chat-v2/chat-v2.singleton.d.ts +8 -0
  124. package/dist/cli/backend/src/services/chat-v2/chat-v2.singleton.d.ts.map +1 -1
  125. package/dist/cli/backend/src/services/chat-v2/chat-v2.singleton.js +10 -0
  126. package/dist/cli/backend/src/services/chat-v2/chat-v2.singleton.js.map +1 -1
  127. package/dist/cli/backend/src/services/open-items/open-item-extractor.d.ts +43 -2
  128. package/dist/cli/backend/src/services/open-items/open-item-extractor.d.ts.map +1 -1
  129. package/dist/cli/backend/src/services/open-items/open-item-extractor.js +124 -13
  130. package/dist/cli/backend/src/services/open-items/open-item-extractor.js.map +1 -1
  131. package/dist/cli/backend/src/services/open-items/open-items.service.d.ts +40 -1
  132. package/dist/cli/backend/src/services/open-items/open-items.service.d.ts.map +1 -1
  133. package/dist/cli/backend/src/services/open-items/open-items.service.js +76 -3
  134. package/dist/cli/backend/src/services/open-items/open-items.service.js.map +1 -1
  135. package/dist/cli/backend/src/services/orc/work-item-destination.d.ts +76 -0
  136. package/dist/cli/backend/src/services/orc/work-item-destination.d.ts.map +1 -1
  137. package/dist/cli/backend/src/services/orc/work-item-destination.js +83 -3
  138. package/dist/cli/backend/src/services/orc/work-item-destination.js.map +1 -1
  139. package/dist/cli/backend/src/services/task-pool/task-pool.service.d.ts +53 -0
  140. package/dist/cli/backend/src/services/task-pool/task-pool.service.d.ts.map +1 -1
  141. package/dist/cli/backend/src/services/task-pool/task-pool.service.js +162 -15
  142. package/dist/cli/backend/src/services/task-pool/task-pool.service.js.map +1 -1
  143. package/dist/cli/backend/src/types/v2/open-item.types.d.ts +2 -0
  144. package/dist/cli/backend/src/types/v2/open-item.types.d.ts.map +1 -1
  145. 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":"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"}
@@ -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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"}