crewly 1.20.182 → 1.20.183

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 (94) hide show
  1. package/config/roles/orchestrator/prompt.md +4 -0
  2. package/config/skills/agent/core/report-status/execute.sh +4 -2
  3. package/dist/backend/backend/src/constants.d.ts +35 -0
  4. package/dist/backend/backend/src/constants.d.ts.map +1 -1
  5. package/dist/backend/backend/src/constants.js +35 -0
  6. package/dist/backend/backend/src/constants.js.map +1 -1
  7. package/dist/backend/backend/src/controllers/chat/agent-reply.controller.d.ts.map +1 -1
  8. package/dist/backend/backend/src/controllers/chat/agent-reply.controller.js +22 -1
  9. package/dist/backend/backend/src/controllers/chat/agent-reply.controller.js.map +1 -1
  10. package/dist/backend/backend/src/controllers/chat/chat.controller.d.ts.map +1 -1
  11. package/dist/backend/backend/src/controllers/chat/chat.controller.js +25 -24
  12. package/dist/backend/backend/src/controllers/chat/chat.controller.js.map +1 -1
  13. package/dist/backend/backend/src/controllers/slack/slack.controller.d.ts.map +1 -1
  14. package/dist/backend/backend/src/controllers/slack/slack.controller.js +15 -0
  15. package/dist/backend/backend/src/controllers/slack/slack.controller.js.map +1 -1
  16. package/dist/backend/backend/src/services/messaging/message-queue.service.d.ts +2 -1
  17. package/dist/backend/backend/src/services/messaging/message-queue.service.d.ts.map +1 -1
  18. package/dist/backend/backend/src/services/messaging/message-queue.service.js +6 -3
  19. package/dist/backend/backend/src/services/messaging/message-queue.service.js.map +1 -1
  20. package/dist/backend/backend/src/services/messaging/queue-processor.service.d.ts.map +1 -1
  21. package/dist/backend/backend/src/services/messaging/queue-processor.service.js +6 -1
  22. package/dist/backend/backend/src/services/messaging/queue-processor.service.js.map +1 -1
  23. package/dist/backend/backend/src/services/orc/orc-delivery-enforcer.service.d.ts +3 -1
  24. package/dist/backend/backend/src/services/orc/orc-delivery-enforcer.service.d.ts.map +1 -1
  25. package/dist/backend/backend/src/services/orc/orc-delivery-enforcer.service.js +7 -4
  26. package/dist/backend/backend/src/services/orc/orc-delivery-enforcer.service.js.map +1 -1
  27. package/dist/backend/backend/src/services/orc/orc-status-router.service.d.ts +110 -0
  28. package/dist/backend/backend/src/services/orc/orc-status-router.service.d.ts.map +1 -0
  29. package/dist/backend/backend/src/services/orc/orc-status-router.service.js +229 -0
  30. package/dist/backend/backend/src/services/orc/orc-status-router.service.js.map +1 -0
  31. package/dist/backend/backend/src/services/orc/orc-status-routing.d.ts +108 -0
  32. package/dist/backend/backend/src/services/orc/orc-status-routing.d.ts.map +1 -0
  33. package/dist/backend/backend/src/services/orc/orc-status-routing.js +145 -0
  34. package/dist/backend/backend/src/services/orc/orc-status-routing.js.map +1 -0
  35. package/dist/backend/backend/src/services/orc/orc-wake-counter.d.ts +86 -0
  36. package/dist/backend/backend/src/services/orc/orc-wake-counter.d.ts.map +1 -0
  37. package/dist/backend/backend/src/services/orc/orc-wake-counter.js +130 -0
  38. package/dist/backend/backend/src/services/orc/orc-wake-counter.js.map +1 -0
  39. package/dist/backend/backend/src/services/orc/work-item-destination.d.ts +33 -0
  40. package/dist/backend/backend/src/services/orc/work-item-destination.d.ts.map +1 -1
  41. package/dist/backend/backend/src/services/orc/work-item-destination.js +49 -0
  42. package/dist/backend/backend/src/services/orc/work-item-destination.js.map +1 -1
  43. package/dist/backend/backend/src/services/orc/work-item-destination.wiring.d.ts +2 -1
  44. package/dist/backend/backend/src/services/orc/work-item-destination.wiring.d.ts.map +1 -1
  45. package/dist/backend/backend/src/services/orc/work-item-destination.wiring.js +10 -4
  46. package/dist/backend/backend/src/services/orc/work-item-destination.wiring.js.map +1 -1
  47. package/dist/backend/backend/src/services/slack/slack-team-channel.service.d.ts +16 -0
  48. package/dist/backend/backend/src/services/slack/slack-team-channel.service.d.ts.map +1 -1
  49. package/dist/backend/backend/src/services/slack/slack-team-channel.service.js +31 -8
  50. package/dist/backend/backend/src/services/slack/slack-team-channel.service.js.map +1 -1
  51. package/dist/backend/backend/src/services/task-pool/give-up/give-up-recovery.service.d.ts.map +1 -1
  52. package/dist/backend/backend/src/services/task-pool/give-up/give-up-recovery.service.js +2 -0
  53. package/dist/backend/backend/src/services/task-pool/give-up/give-up-recovery.service.js.map +1 -1
  54. package/dist/backend/backend/src/services/task-pool/task-pool.service.d.ts +23 -0
  55. package/dist/backend/backend/src/services/task-pool/task-pool.service.d.ts.map +1 -1
  56. package/dist/backend/backend/src/services/task-pool/task-pool.service.js +61 -0
  57. package/dist/backend/backend/src/services/task-pool/task-pool.service.js.map +1 -1
  58. package/dist/backend/build-info.json +2 -2
  59. package/dist/cli/backend/src/constants.d.ts +35 -0
  60. package/dist/cli/backend/src/constants.d.ts.map +1 -1
  61. package/dist/cli/backend/src/constants.js +35 -0
  62. package/dist/cli/backend/src/constants.js.map +1 -1
  63. package/dist/cli/backend/src/services/messaging/message-queue.service.d.ts +2 -1
  64. package/dist/cli/backend/src/services/messaging/message-queue.service.d.ts.map +1 -1
  65. package/dist/cli/backend/src/services/messaging/message-queue.service.js +6 -3
  66. package/dist/cli/backend/src/services/messaging/message-queue.service.js.map +1 -1
  67. package/dist/cli/backend/src/services/messaging/queue-processor.service.d.ts.map +1 -1
  68. package/dist/cli/backend/src/services/messaging/queue-processor.service.js +6 -1
  69. package/dist/cli/backend/src/services/messaging/queue-processor.service.js.map +1 -1
  70. package/dist/cli/backend/src/services/orc/orc-delivery-enforcer.service.d.ts +3 -1
  71. package/dist/cli/backend/src/services/orc/orc-delivery-enforcer.service.d.ts.map +1 -1
  72. package/dist/cli/backend/src/services/orc/orc-delivery-enforcer.service.js +7 -4
  73. package/dist/cli/backend/src/services/orc/orc-delivery-enforcer.service.js.map +1 -1
  74. package/dist/cli/backend/src/services/orc/orc-status-routing.d.ts +108 -0
  75. package/dist/cli/backend/src/services/orc/orc-status-routing.d.ts.map +1 -0
  76. package/dist/cli/backend/src/services/orc/orc-status-routing.js +145 -0
  77. package/dist/cli/backend/src/services/orc/orc-status-routing.js.map +1 -0
  78. package/dist/cli/backend/src/services/orc/orc-wake-counter.d.ts +86 -0
  79. package/dist/cli/backend/src/services/orc/orc-wake-counter.d.ts.map +1 -0
  80. package/dist/cli/backend/src/services/orc/orc-wake-counter.js +130 -0
  81. package/dist/cli/backend/src/services/orc/orc-wake-counter.js.map +1 -0
  82. package/dist/cli/backend/src/services/orc/work-item-destination.d.ts +198 -0
  83. package/dist/cli/backend/src/services/orc/work-item-destination.d.ts.map +1 -0
  84. package/dist/cli/backend/src/services/orc/work-item-destination.js +288 -0
  85. package/dist/cli/backend/src/services/orc/work-item-destination.js.map +1 -0
  86. package/dist/cli/backend/src/services/slack/slack-team-channel.service.d.ts +16 -0
  87. package/dist/cli/backend/src/services/slack/slack-team-channel.service.d.ts.map +1 -1
  88. package/dist/cli/backend/src/services/slack/slack-team-channel.service.js +31 -8
  89. package/dist/cli/backend/src/services/slack/slack-team-channel.service.js.map +1 -1
  90. package/dist/cli/backend/src/services/task-pool/task-pool.service.d.ts +23 -0
  91. package/dist/cli/backend/src/services/task-pool/task-pool.service.d.ts.map +1 -1
  92. package/dist/cli/backend/src/services/task-pool/task-pool.service.js +61 -0
  93. package/dist/cli/backend/src/services/task-pool/task-pool.service.js.map +1 -1
  94. package/package.json +1 -1
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Who an agent status report wakes (specs/2026-10-01-orc-status-wakes.md).
3
+ *
4
+ * Every `report-status` line used to be queued for the orchestrator. On
5
+ * 2026-09-29 that was 134 full-context orchestrator turns, almost all of them
6
+ * team members' [DONE] lines about work their own team lead owns and reviews.
7
+ * Now the report goes to whoever is responsible:
8
+ *
9
+ * | Report | Goes to |
10
+ * |---|---|
11
+ * | [IN_PROGRESS] [ACTIVE] [READY] [WORKING] [IDLE] … | recorded only |
12
+ * | [DONE] that a pending owner delivery is waiting for | orchestrator |
13
+ * | [DONE] on work the orchestrator delegated | orchestrator |
14
+ * | [DONE] on anyone else's work item | recorded (the lead's review path takes it) |
15
+ * | [DONE] with no work item | digest (actionable only without a lead) |
16
+ * | [BLOCKED] [FAILED] [ERROR] | the sender's lead; orchestrator when there is none or the lead sent it |
17
+ * | [MILESTONE] | orchestrator (it forwards milestones to the owner) |
18
+ * | other markers | digest (actionable only on orchestrator work or without a lead) |
19
+ * | no marker (an answer someone waits for) | orchestrator, as before |
20
+ *
21
+ * Pure functions only; {@link OrcStatusRouterService} carries out the plan.
22
+ *
23
+ * @module services/orc/orc-status-routing
24
+ */
25
+ import type { WorkItem } from '../../types/v2/work-item.types.js';
26
+ /** Why the orchestrator is woken (the hourly counter's categories). */
27
+ export type OrcWakeCategory = 'owner' | 'delegated-done' | 'escalation' | 'digest' | 'other';
28
+ /** What happens to one status report. */
29
+ export type OrcStatusRoute =
30
+ /** Status store / UI / ticket log only */
31
+ {
32
+ action: 'record';
33
+ reason: string;
34
+ }
35
+ /** Wake the orchestrator now */
36
+ | {
37
+ action: 'orc';
38
+ category: Exclude<OrcWakeCategory, 'digest'>;
39
+ reason: string;
40
+ }
41
+ /** Hand it to the sender's team lead */
42
+ | {
43
+ action: 'team-lead';
44
+ lead: string;
45
+ reason: string;
46
+ }
47
+ /** Hold for the next digest; only actionable entries make a digest go out */
48
+ | {
49
+ action: 'digest';
50
+ actionable: boolean;
51
+ reason: string;
52
+ };
53
+ /** Inputs of {@link planOrcStatusRoute}. */
54
+ export interface OrcStatusRouteInput {
55
+ /** The report text (`[DONE] Agent x: …`) */
56
+ content: string;
57
+ /** Reporting agent session */
58
+ sender: string;
59
+ /** The work item the report is about, when known */
60
+ workItem: WorkItem | null;
61
+ /** The sender's lead (parent member, else team lead), never the sender itself */
62
+ lead: string | null;
63
+ /** True when the sender leads its own team (reason text only; a lead with a parent still reports up) */
64
+ senderIsLead: boolean;
65
+ /** True when the owner is waiting on the orchestrator to deliver this (OrcDeliveryEnforcer tracked it) */
66
+ deliveryOwed: boolean;
67
+ /** Orchestrator check for a session / name */
68
+ isOrchestrator: (name: string) => boolean;
69
+ }
70
+ /**
71
+ * The bracketed marker a report starts with, upper-cased.
72
+ *
73
+ * @param content - Report text
74
+ * @returns e.g. `DONE`, or null when the text has no leading marker
75
+ */
76
+ export declare function statusMarkerOf(content: string): string | null;
77
+ /**
78
+ * Whether the orchestrator delegated (or created) a work item: its owner is
79
+ * the orchestrator, or the orchestrator stamped itself as the delegator.
80
+ *
81
+ * @param wi - Work item
82
+ * @param isOrchestrator - Orchestrator check
83
+ * @returns True for orchestrator work
84
+ */
85
+ export declare function isOrcDelegated(wi: WorkItem, isOrchestrator: (name: string) => boolean): boolean;
86
+ /**
87
+ * The work item a report is about: the one it names; else the sender's
88
+ * running/accepted item (newest); else the sender's most recently finished
89
+ * item, when it finished within {@link ORC_WAKE_CONSTANTS.RECENT_COMPLETION_MS}.
90
+ *
91
+ * `report-status` posts the line BEFORE it completes the item, so the
92
+ * running item is normally the answer.
93
+ *
94
+ * @param items - Pool items
95
+ * @param sender - Reporting agent
96
+ * @param workItemId - Id the report named, if any
97
+ * @param now - Clock (epoch ms)
98
+ * @returns The item, or null
99
+ */
100
+ export declare function findStatusWorkItem(items: readonly WorkItem[], sender: string, workItemId: string | undefined, now: number): WorkItem | null;
101
+ /**
102
+ * Decide where one agent status report goes.
103
+ *
104
+ * @param input - Report, sender, its work item and lead
105
+ * @returns The route
106
+ */
107
+ export declare function planOrcStatusRoute(input: OrcStatusRouteInput): OrcStatusRoute;
108
+ //# sourceMappingURL=orc-status-routing.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"orc-status-routing.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/orc/orc-status-routing.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAGH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mCAAmC,CAAC;AAElE,uEAAuE;AACvE,MAAM,MAAM,eAAe,GAAG,OAAO,GAAG,gBAAgB,GAAG,YAAY,GAAG,QAAQ,GAAG,OAAO,CAAC;AAE7F,yCAAyC;AACzC,MAAM,MAAM,cAAc;AACxB,0CAA0C;AACxC;IAAE,MAAM,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE;AACtC,gCAAgC;GAC9B;IAAE,MAAM,EAAE,KAAK,CAAC;IAAC,QAAQ,EAAE,OAAO,CAAC,eAAe,EAAE,QAAQ,CAAC,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE;AACjF,wCAAwC;GACtC;IAAE,MAAM,EAAE,WAAW,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE;AACvD,6EAA6E;GAC3E;IAAE,MAAM,EAAE,QAAQ,CAAC;IAAC,UAAU,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAE9D,4CAA4C;AAC5C,MAAM,WAAW,mBAAmB;IAClC,4CAA4C;IAC5C,OAAO,EAAE,MAAM,CAAC;IAChB,8BAA8B;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,oDAAoD;IACpD,QAAQ,EAAE,QAAQ,GAAG,IAAI,CAAC;IAC1B,iFAAiF;IACjF,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,wGAAwG;IACxG,YAAY,EAAE,OAAO,CAAC;IACtB,0GAA0G;IAC1G,YAAY,EAAE,OAAO,CAAC;IACtB,8CAA8C;IAC9C,cAAc,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;CAC3C;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAG7D;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,EAAE,EAAE,QAAQ,EAAE,cAAc,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,GAAG,OAAO,CAQ/F;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,SAAS,QAAQ,EAAE,EAC1B,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,GAAG,EAAE,MAAM,GACV,QAAQ,GAAG,IAAI,CA0BjB;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,mBAAmB,GAAG,cAAc,CA2C7E"}
@@ -0,0 +1,145 @@
1
+ /**
2
+ * Who an agent status report wakes (specs/2026-10-01-orc-status-wakes.md).
3
+ *
4
+ * Every `report-status` line used to be queued for the orchestrator. On
5
+ * 2026-09-29 that was 134 full-context orchestrator turns, almost all of them
6
+ * team members' [DONE] lines about work their own team lead owns and reviews.
7
+ * Now the report goes to whoever is responsible:
8
+ *
9
+ * | Report | Goes to |
10
+ * |---|---|
11
+ * | [IN_PROGRESS] [ACTIVE] [READY] [WORKING] [IDLE] … | recorded only |
12
+ * | [DONE] that a pending owner delivery is waiting for | orchestrator |
13
+ * | [DONE] on work the orchestrator delegated | orchestrator |
14
+ * | [DONE] on anyone else's work item | recorded (the lead's review path takes it) |
15
+ * | [DONE] with no work item | digest (actionable only without a lead) |
16
+ * | [BLOCKED] [FAILED] [ERROR] | the sender's lead; orchestrator when there is none or the lead sent it |
17
+ * | [MILESTONE] | orchestrator (it forwards milestones to the owner) |
18
+ * | other markers | digest (actionable only on orchestrator work or without a lead) |
19
+ * | no marker (an answer someone waits for) | orchestrator, as before |
20
+ *
21
+ * Pure functions only; {@link OrcStatusRouterService} carries out the plan.
22
+ *
23
+ * @module services/orc/orc-status-routing
24
+ */
25
+ import { ORC_WAKE_CONSTANTS } from '../../constants.js';
26
+ /**
27
+ * The bracketed marker a report starts with, upper-cased.
28
+ *
29
+ * @param content - Report text
30
+ * @returns e.g. `DONE`, or null when the text has no leading marker
31
+ */
32
+ export function statusMarkerOf(content) {
33
+ const m = /^\s*\[([A-Za-z_ ]{2,40})\]/.exec(content ?? '');
34
+ return m ? m[1].trim().toUpperCase() : null;
35
+ }
36
+ /**
37
+ * Whether the orchestrator delegated (or created) a work item: its owner is
38
+ * the orchestrator, or the orchestrator stamped itself as the delegator.
39
+ *
40
+ * @param wi - Work item
41
+ * @param isOrchestrator - Orchestrator check
42
+ * @returns True for orchestrator work
43
+ */
44
+ export function isOrcDelegated(wi, isOrchestrator) {
45
+ if (wi.owner === 'orchestrator')
46
+ return true;
47
+ const meta = (wi.metadata ?? {});
48
+ for (const key of ['delegatedBy', 'createdBy']) {
49
+ const v = meta[key];
50
+ if (typeof v === 'string' && v && isOrchestrator(v))
51
+ return true;
52
+ }
53
+ return false;
54
+ }
55
+ /**
56
+ * The work item a report is about: the one it names; else the sender's
57
+ * running/accepted item (newest); else the sender's most recently finished
58
+ * item, when it finished within {@link ORC_WAKE_CONSTANTS.RECENT_COMPLETION_MS}.
59
+ *
60
+ * `report-status` posts the line BEFORE it completes the item, so the
61
+ * running item is normally the answer.
62
+ *
63
+ * @param items - Pool items
64
+ * @param sender - Reporting agent
65
+ * @param workItemId - Id the report named, if any
66
+ * @param now - Clock (epoch ms)
67
+ * @returns The item, or null
68
+ */
69
+ export function findStatusWorkItem(items, sender, workItemId, now) {
70
+ if (workItemId) {
71
+ const named = items.find((wi) => wi.id === workItemId);
72
+ if (named)
73
+ return named;
74
+ }
75
+ let running = null;
76
+ let runningAt = -Infinity;
77
+ let finished = null;
78
+ let finishedAt = -Infinity;
79
+ for (const wi of items) {
80
+ if (wi.target !== sender)
81
+ continue;
82
+ if (wi.status === 'running' || wi.status === 'accepted') {
83
+ const at = Date.parse(wi.startedAt ?? wi.createdAt) || 0;
84
+ if (at > runningAt) {
85
+ running = wi;
86
+ runningAt = at;
87
+ }
88
+ continue;
89
+ }
90
+ const doneAt = Date.parse(wi.completedAt ?? '') || 0;
91
+ if (doneAt && now - doneAt <= ORC_WAKE_CONSTANTS.RECENT_COMPLETION_MS && doneAt > finishedAt) {
92
+ finished = wi;
93
+ finishedAt = doneAt;
94
+ }
95
+ }
96
+ return running ?? finished;
97
+ }
98
+ /**
99
+ * Decide where one agent status report goes.
100
+ *
101
+ * @param input - Report, sender, its work item and lead
102
+ * @returns The route
103
+ */
104
+ export function planOrcStatusRoute(input) {
105
+ const { content, sender, workItem, lead, senderIsLead, deliveryOwed, isOrchestrator } = input;
106
+ const marker = statusMarkerOf(content);
107
+ const hasLead = Boolean(lead) && lead !== sender && !isOrchestrator(String(lead));
108
+ if (ORC_WAKE_CONSTANTS.RECORD_ONLY_MARKERS.test(content)) {
109
+ return { action: 'record', reason: `progress marker [${marker}]` };
110
+ }
111
+ if (deliveryOwed) {
112
+ return { action: 'orc', category: 'owner', reason: 'the owner is waiting on the orchestrator to deliver this' };
113
+ }
114
+ if (!marker) {
115
+ return { action: 'orc', category: 'other', reason: 'not a status line — someone may be waiting for this answer' };
116
+ }
117
+ if (ORC_WAKE_CONSTANTS.ALWAYS_ORC_MARKERS.test(content)) {
118
+ return { action: 'orc', category: 'other', reason: `[${marker}] is forwarded to the owner by the orchestrator` };
119
+ }
120
+ const orcWork = workItem ? isOrcDelegated(workItem, isOrchestrator) : false;
121
+ if (ORC_WAKE_CONSTANTS.DONE_MARKERS.test(content)) {
122
+ if (workItem && orcWork) {
123
+ return { action: 'orc', category: 'delegated-done', reason: `work item ${workItem.id} was delegated by the orchestrator` };
124
+ }
125
+ if (workItem) {
126
+ return { action: 'record', reason: `work item ${workItem.id} is owned by ${workItem.owner}; its review path takes the [DONE]` };
127
+ }
128
+ return hasLead
129
+ ? { action: 'digest', actionable: false, reason: `no work item; ${sender} reports to ${lead}` }
130
+ : { action: 'digest', actionable: true, reason: `no work item and no lead above ${sender}` };
131
+ }
132
+ if (ORC_WAKE_CONSTANTS.ATTENTION_MARKERS.test(content)) {
133
+ if (hasLead && lead)
134
+ return { action: 'team-lead', lead, reason: `[${marker}] goes to ${sender}'s lead first` };
135
+ return { action: 'orc', category: 'escalation', reason: senderIsLead ? `[${marker}] from a team lead` : `[${marker}] and ${sender} has no lead` };
136
+ }
137
+ // Structured reports and unknown markers.
138
+ const actionable = orcWork || !hasLead;
139
+ return {
140
+ action: 'digest',
141
+ actionable,
142
+ reason: actionable ? `[${marker}] ${orcWork ? 'on orchestrator work' : 'from an agent with no lead'}` : `[${marker}] on work its lead owns`,
143
+ };
144
+ }
145
+ //# sourceMappingURL=orc-status-routing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"orc-status-routing.js","sourceRoot":"","sources":["../../../../../../backend/src/services/orc/orc-status-routing.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Hourly count of orchestrator turns, by why it was woken
3
+ * (specs/2026-10-01-orc-status-wakes.md).
4
+ *
5
+ * Every orchestrator turn re-reads its whole context (~70k tokens on
6
+ * 2026-09-29), so the number of turns IS the cost. Once an hour the counter
7
+ * logs one line:
8
+ *
9
+ * orc wakes: 14 (owner 3, delegated-done 2, escalations 1, digest 2, other 6)
10
+ *
11
+ * - `N` and `owner` are counted when the queue processor starts an
12
+ * orchestrator turn (`owner` = any non-system source: Slack, web chat, …).
13
+ * - `delegated-done`, `escalations`, `digest` are counted when the status
14
+ * router decides to wake the orchestrator (several queued system events
15
+ * can coalesce into one turn, so these may add up to more than the turns
16
+ * they caused).
17
+ * - `other` = turns that are neither owner messages nor status-router wakes
18
+ * (cron, wiki, triggers, enforcer reminders, …).
19
+ *
20
+ * @module services/orc/orc-wake-counter
21
+ */
22
+ import type { OrcWakeCategory } from './orc-status-routing.js';
23
+ /** One hour's numbers. */
24
+ export interface OrcWakeCounts {
25
+ /** Orchestrator turns started */
26
+ turns: number;
27
+ /** Turns for an owner message */
28
+ owner: number;
29
+ /** Status-router wakes: [DONE] on orchestrator-delegated work (incl. owed deliveries) */
30
+ delegatedDone: number;
31
+ /** Status-router wakes: [BLOCKED]/[FAILED] with no lead to take it */
32
+ escalations: number;
33
+ /** Status digests sent */
34
+ digest: number;
35
+ /** Status-router wakes for an answer that was not a status line */
36
+ statusOther: number;
37
+ }
38
+ /**
39
+ * The log line for one period.
40
+ *
41
+ * @param c - Counts
42
+ * @returns `orc wakes: N (owner X, delegated-done Y, escalations Z, digest W, other V)`
43
+ */
44
+ export declare function formatOrcWakeLine(c: OrcWakeCounts): string;
45
+ /** Counts orchestrator wakes and logs them once per period. */
46
+ export declare class OrcWakeCounter {
47
+ private readonly now;
48
+ private static instance;
49
+ private counts;
50
+ private periodStart;
51
+ private timer;
52
+ private readonly logger;
53
+ /**
54
+ * @param now - Clock (tests)
55
+ */
56
+ constructor(now?: () => number);
57
+ /** The process-wide counter. */
58
+ static getInstance(): OrcWakeCounter;
59
+ /** Drop the singleton (tests). */
60
+ static resetInstance(): void;
61
+ /** Log every {@link ORC_WAKE_CONSTANTS.COUNTER_LOG_INTERVAL_MS}. Idempotent. */
62
+ start(): void;
63
+ /** Stop the periodic log. */
64
+ stop(): void;
65
+ /**
66
+ * An orchestrator turn started (queue processor).
67
+ *
68
+ * @param source - Message source of the turn
69
+ */
70
+ noteTurn(source: string): void;
71
+ /**
72
+ * The status router decided to wake the orchestrator.
73
+ *
74
+ * @param category - Why
75
+ */
76
+ noteRouted(category: OrcWakeCategory): void;
77
+ /** The current period's numbers (copy). */
78
+ snapshot(): OrcWakeCounts;
79
+ /**
80
+ * Log the period's line and start a new period.
81
+ *
82
+ * @returns The logged line
83
+ */
84
+ flush(): string;
85
+ }
86
+ //# sourceMappingURL=orc-wake-counter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"orc-wake-counter.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/orc/orc-wake-counter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAIH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAE/D,0BAA0B;AAC1B,MAAM,WAAW,aAAa;IAC5B,iCAAiC;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,iCAAiC;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,yFAAyF;IACzF,aAAa,EAAE,MAAM,CAAC;IACtB,sEAAsE;IACtE,WAAW,EAAE,MAAM,CAAC;IACpB,0BAA0B;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,mEAAmE;IACnE,WAAW,EAAE,MAAM,CAAC;CACrB;AAOD;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,aAAa,GAAG,MAAM,CAI1D;AAED,+DAA+D;AAC/D,qBAAa,cAAc;IAUb,OAAO,CAAC,QAAQ,CAAC,GAAG;IAThC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAA+B;IACtD,OAAO,CAAC,MAAM,CAAyB;IACvC,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,KAAK,CAA+B;IAC5C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAkB;IAEzC;;OAEG;gBAC0B,GAAG,GAAE,MAAM,MAAiB;IAKzD,gCAAgC;IAChC,MAAM,CAAC,WAAW,IAAI,cAAc;IAKpC,kCAAkC;IAClC,MAAM,CAAC,aAAa,IAAI,IAAI;IAK5B,gFAAgF;IAChF,KAAK,IAAI,IAAI;IAMb,6BAA6B;IAC7B,IAAI,IAAI,IAAI;IAKZ;;;;OAIG;IACH,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAK9B;;;;OAIG;IACH,UAAU,CAAC,QAAQ,EAAE,eAAe,GAAG,IAAI;IAiB3C,2CAA2C;IAC3C,QAAQ,IAAI,aAAa;IAIzB;;;;OAIG;IACH,KAAK,IAAI,MAAM;CAUhB"}
@@ -0,0 +1,130 @@
1
+ /**
2
+ * Hourly count of orchestrator turns, by why it was woken
3
+ * (specs/2026-10-01-orc-status-wakes.md).
4
+ *
5
+ * Every orchestrator turn re-reads its whole context (~70k tokens on
6
+ * 2026-09-29), so the number of turns IS the cost. Once an hour the counter
7
+ * logs one line:
8
+ *
9
+ * orc wakes: 14 (owner 3, delegated-done 2, escalations 1, digest 2, other 6)
10
+ *
11
+ * - `N` and `owner` are counted when the queue processor starts an
12
+ * orchestrator turn (`owner` = any non-system source: Slack, web chat, …).
13
+ * - `delegated-done`, `escalations`, `digest` are counted when the status
14
+ * router decides to wake the orchestrator (several queued system events
15
+ * can coalesce into one turn, so these may add up to more than the turns
16
+ * they caused).
17
+ * - `other` = turns that are neither owner messages nor status-router wakes
18
+ * (cron, wiki, triggers, enforcer reminders, …).
19
+ *
20
+ * @module services/orc/orc-wake-counter
21
+ */
22
+ import { MESSAGE_SOURCES, ORC_WAKE_CONSTANTS } from '../../constants.js';
23
+ import { LoggerService } from '../core/logger.service.js';
24
+ /** A fresh, zeroed count. */
25
+ function zero() {
26
+ return { turns: 0, owner: 0, delegatedDone: 0, escalations: 0, digest: 0, statusOther: 0 };
27
+ }
28
+ /**
29
+ * The log line for one period.
30
+ *
31
+ * @param c - Counts
32
+ * @returns `orc wakes: N (owner X, delegated-done Y, escalations Z, digest W, other V)`
33
+ */
34
+ export function formatOrcWakeLine(c) {
35
+ const routed = c.delegatedDone + c.escalations + c.digest;
36
+ const other = Math.max(0, c.turns - c.owner - routed);
37
+ return `orc wakes: ${c.turns} (owner ${c.owner}, delegated-done ${c.delegatedDone}, escalations ${c.escalations}, digest ${c.digest}, other ${other})`;
38
+ }
39
+ /** Counts orchestrator wakes and logs them once per period. */
40
+ export class OrcWakeCounter {
41
+ now;
42
+ static instance = null;
43
+ counts = zero();
44
+ periodStart;
45
+ timer = null;
46
+ logger;
47
+ /**
48
+ * @param now - Clock (tests)
49
+ */
50
+ constructor(now = Date.now) {
51
+ this.now = now;
52
+ this.periodStart = now();
53
+ this.logger = LoggerService.getInstance().createComponentLogger('OrcWakes');
54
+ }
55
+ /** The process-wide counter. */
56
+ static getInstance() {
57
+ if (!OrcWakeCounter.instance)
58
+ OrcWakeCounter.instance = new OrcWakeCounter();
59
+ return OrcWakeCounter.instance;
60
+ }
61
+ /** Drop the singleton (tests). */
62
+ static resetInstance() {
63
+ OrcWakeCounter.instance?.stop();
64
+ OrcWakeCounter.instance = null;
65
+ }
66
+ /** Log every {@link ORC_WAKE_CONSTANTS.COUNTER_LOG_INTERVAL_MS}. Idempotent. */
67
+ start() {
68
+ if (this.timer)
69
+ return;
70
+ this.timer = setInterval(() => this.flush(), ORC_WAKE_CONSTANTS.COUNTER_LOG_INTERVAL_MS);
71
+ this.timer.unref?.();
72
+ }
73
+ /** Stop the periodic log. */
74
+ stop() {
75
+ if (this.timer)
76
+ clearInterval(this.timer);
77
+ this.timer = null;
78
+ }
79
+ /**
80
+ * An orchestrator turn started (queue processor).
81
+ *
82
+ * @param source - Message source of the turn
83
+ */
84
+ noteTurn(source) {
85
+ this.counts.turns += 1;
86
+ if (source !== MESSAGE_SOURCES.SYSTEM_EVENT)
87
+ this.counts.owner += 1;
88
+ }
89
+ /**
90
+ * The status router decided to wake the orchestrator.
91
+ *
92
+ * @param category - Why
93
+ */
94
+ noteRouted(category) {
95
+ switch (category) {
96
+ case 'owner':
97
+ case 'delegated-done':
98
+ this.counts.delegatedDone += 1;
99
+ break;
100
+ case 'escalation':
101
+ this.counts.escalations += 1;
102
+ break;
103
+ case 'digest':
104
+ this.counts.digest += 1;
105
+ break;
106
+ default:
107
+ this.counts.statusOther += 1;
108
+ }
109
+ }
110
+ /** The current period's numbers (copy). */
111
+ snapshot() {
112
+ return { ...this.counts };
113
+ }
114
+ /**
115
+ * Log the period's line and start a new period.
116
+ *
117
+ * @returns The logged line
118
+ */
119
+ flush() {
120
+ const line = formatOrcWakeLine(this.counts);
121
+ this.logger.info(line, {
122
+ since: new Date(this.periodStart).toISOString(),
123
+ ...this.counts,
124
+ });
125
+ this.counts = zero();
126
+ this.periodStart = this.now();
127
+ return line;
128
+ }
129
+ }
130
+ //# sourceMappingURL=orc-wake-counter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"orc-wake-counter.js","sourceRoot":"","sources":["../../../../../../backend/src/services/orc/orc-wake-counter.ts"],"names":[],"mappings":"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"}
@@ -30,6 +30,7 @@ export type WorkItemOrigin =
30
30
  conversationId?: string;
31
31
  slackChannelId?: string;
32
32
  threadTs?: string;
33
+ chatThreadId?: string;
33
34
  }
34
35
  /** Work on a project ticket */
35
36
  | {
@@ -117,6 +118,38 @@ export declare function currentWorkItemOf(items: readonly WorkItem[], session: s
117
118
  * @returns The origin, or null when nothing tells where it came from
118
119
  */
119
120
  export declare function originOfWorkItem(wi: WorkItem): WorkItemOrigin | null;
121
+ /**
122
+ * The origin an owner turn gives the work started from it.
123
+ *
124
+ * @param turn - The owner's turn origin (OrcReplyRouteService)
125
+ * @returns An owner origin
126
+ */
127
+ export declare function ownerOriginFromTurn(turn: TurnOrigin): WorkItemOrigin;
128
+ /** Inputs of {@link inheritedOrigin}. */
129
+ export interface InheritOriginInput {
130
+ /** The item being created */
131
+ workItem: WorkItem;
132
+ /** The item it continues (verify / retry / subtask parent), when it has one */
133
+ parent: WorkItem | null;
134
+ /** Where the creating agent's current work came from (its plan), when an agent creates it */
135
+ creatorDestination: WorkDestination | null;
136
+ /** The creating agent's current work item */
137
+ creatorWorkItem: WorkItem | null;
138
+ }
139
+ /**
140
+ * The origin a new work item inherits (owner request → delegate → verify /
141
+ * retry / subtask). Incident 2026-10-01: Atlas delegated the owner's
142
+ * #morning-brief question to Sage; Sage's [DONE] made a verify item for
143
+ * Atlas with no origin, and the answer's file landed in an unrelated thread.
144
+ *
145
+ * Order: an origin already stamped wins (null = keep it); then the parent's;
146
+ * then the creator's current owner request; then the creator's current
147
+ * work item's.
148
+ *
149
+ * @param input - New item, its parent, the creator's current work
150
+ * @returns The origin to stamp, or null (nothing to inherit / already stamped)
151
+ */
152
+ export declare function inheritedOrigin(input: InheritOriginInput): WorkItemOrigin | null;
120
153
  /**
121
154
  * Shape check for a stamped origin.
122
155
  *
@@ -1 +1 @@
1
- {"version":3,"file":"work-item-destination.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/orc/work-item-destination.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"work-item-destination.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/orc/work-item-destination.ts"],"names":[],"mappings":"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"}
@@ -21,6 +21,7 @@
21
21
  * @module services/orc/work-item-destination
22
22
  */
23
23
  import { WORK_ITEM_DESTINATION_CONSTANTS } from '../../constants.js';
24
+ import { parseSlackThreadKey } from '../slack/slack-thread-key.js';
24
25
  /** Statuses in which a work item is the agent's current work. */
25
26
  const CURRENT_STATUSES = new Set(['running', 'accepted']);
26
27
  /**
@@ -113,6 +114,53 @@ export function originOfWorkItem(wi) {
113
114
  }
114
115
  return null;
115
116
  }
117
+ /**
118
+ * The origin an owner turn gives the work started from it.
119
+ *
120
+ * @param turn - The owner's turn origin (OrcReplyRouteService)
121
+ * @returns An owner origin
122
+ */
123
+ export function ownerOriginFromTurn(turn) {
124
+ const key = parseSlackThreadKey(turn.slackThreadKey);
125
+ const slackChannelId = key?.slackChannelId ?? turn.slackChannelId;
126
+ const threadTs = key?.threadTs ?? turn.slackThreadTs;
127
+ return {
128
+ kind: 'owner',
129
+ conversationId: turn.conversationId,
130
+ ...(slackChannelId ? { slackChannelId } : {}),
131
+ ...(threadTs ? { threadTs } : {}),
132
+ ...(turn.chatThreadId ? { chatThreadId: turn.chatThreadId } : {}),
133
+ };
134
+ }
135
+ /**
136
+ * The origin a new work item inherits (owner request → delegate → verify /
137
+ * retry / subtask). Incident 2026-10-01: Atlas delegated the owner's
138
+ * #morning-brief question to Sage; Sage's [DONE] made a verify item for
139
+ * Atlas with no origin, and the answer's file landed in an unrelated thread.
140
+ *
141
+ * Order: an origin already stamped wins (null = keep it); then the parent's;
142
+ * then the creator's current owner request; then the creator's current
143
+ * work item's.
144
+ *
145
+ * @param input - New item, its parent, the creator's current work
146
+ * @returns The origin to stamp, or null (nothing to inherit / already stamped)
147
+ */
148
+ export function inheritedOrigin(input) {
149
+ const meta = (input.workItem.metadata ?? {});
150
+ if (isWorkItemOrigin(meta[WORK_ITEM_DESTINATION_CONSTANTS.METADATA_KEY]))
151
+ return null;
152
+ if (input.parent) {
153
+ const fromParent = originOfWorkItem(input.parent);
154
+ if (fromParent)
155
+ return fromParent;
156
+ }
157
+ const dest = input.creatorDestination;
158
+ if (dest?.kind === 'owner-origin')
159
+ return ownerOriginFromTurn(dest.origin);
160
+ if (input.creatorWorkItem)
161
+ return originOfWorkItem(input.creatorWorkItem);
162
+ return null;
163
+ }
116
164
  /**
117
165
  * Shape check for a stamped origin.
118
166
  *
@@ -182,6 +230,7 @@ export function planWorkDestination(input) {
182
230
  ...(origin.slackChannelId ? { slackChannelId: origin.slackChannelId } : {}),
183
231
  ...(origin.threadTs ? { slackThreadTs: origin.threadTs } : {}),
184
232
  ...(threadKey ? { slackThreadKey: threadKey } : {}),
233
+ ...(origin.chatThreadId ? { chatThreadId: origin.chatThreadId } : {}),
185
234
  receivedAt: startOf(workItem),
186
235
  },
187
236
  reason: `work item ${workItem.id} was asked by the owner`,