crewly 1.20.161 → 1.20.162

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 (137) hide show
  1. package/config/roles/orchestrator/prompt.md +2 -0
  2. package/config/roles/team-leader/prompt.md +12 -0
  3. package/config/roles/team-leader/tl-addon.md +12 -0
  4. package/config/skills/agent/core/project-tickets/SKILL.md +32 -1
  5. package/config/skills/agent/core/project-tickets/execute.sh +56 -2
  6. package/config/skills/agent/core/project-tickets/execute.test.sh +22 -0
  7. package/dist/backend/backend/src/constants.d.ts +84 -1
  8. package/dist/backend/backend/src/constants.d.ts.map +1 -1
  9. package/dist/backend/backend/src/constants.js +105 -16
  10. package/dist/backend/backend/src/constants.js.map +1 -1
  11. package/dist/backend/backend/src/controllers/project-tickets/project-tickets.controller.d.ts +43 -0
  12. package/dist/backend/backend/src/controllers/project-tickets/project-tickets.controller.d.ts.map +1 -1
  13. package/dist/backend/backend/src/controllers/project-tickets/project-tickets.controller.js +85 -0
  14. package/dist/backend/backend/src/controllers/project-tickets/project-tickets.controller.js.map +1 -1
  15. package/dist/backend/backend/src/controllers/project-tickets/project-tickets.routes.d.ts +12 -0
  16. package/dist/backend/backend/src/controllers/project-tickets/project-tickets.routes.d.ts.map +1 -1
  17. package/dist/backend/backend/src/controllers/project-tickets/project-tickets.routes.js +19 -1
  18. package/dist/backend/backend/src/controllers/project-tickets/project-tickets.routes.js.map +1 -1
  19. package/dist/backend/backend/src/index.d.ts.map +1 -1
  20. package/dist/backend/backend/src/index.js +30 -1
  21. package/dist/backend/backend/src/index.js.map +1 -1
  22. package/dist/backend/backend/src/models/Project.d.ts +3 -0
  23. package/dist/backend/backend/src/models/Project.d.ts.map +1 -1
  24. package/dist/backend/backend/src/models/Project.js +7 -0
  25. package/dist/backend/backend/src/models/Project.js.map +1 -1
  26. package/dist/backend/backend/src/routes/api.routes.d.ts.map +1 -1
  27. package/dist/backend/backend/src/routes/api.routes.js +3 -1
  28. package/dist/backend/backend/src/routes/api.routes.js.map +1 -1
  29. package/dist/backend/backend/src/services/agent/oauth-relogin-monitor.service.d.ts +66 -13
  30. package/dist/backend/backend/src/services/agent/oauth-relogin-monitor.service.d.ts.map +1 -1
  31. package/dist/backend/backend/src/services/agent/oauth-relogin-monitor.service.js +161 -32
  32. package/dist/backend/backend/src/services/agent/oauth-relogin-monitor.service.js.map +1 -1
  33. package/dist/backend/backend/src/services/project-tickets/project-ticket-workflow.service.d.ts +48 -0
  34. package/dist/backend/backend/src/services/project-tickets/project-ticket-workflow.service.d.ts.map +1 -1
  35. package/dist/backend/backend/src/services/project-tickets/project-ticket-workflow.service.js +77 -1
  36. package/dist/backend/backend/src/services/project-tickets/project-ticket-workflow.service.js.map +1 -1
  37. package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-decision.d.ts +223 -0
  38. package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-decision.d.ts.map +1 -0
  39. package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-decision.js +240 -0
  40. package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-decision.js.map +1 -0
  41. package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-messages.d.ts +94 -0
  42. package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-messages.d.ts.map +1 -0
  43. package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-messages.js +195 -0
  44. package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-messages.js.map +1 -0
  45. package/dist/backend/backend/src/services/project-tickets/ticket-autopilot.service.d.ts +286 -0
  46. package/dist/backend/backend/src/services/project-tickets/ticket-autopilot.service.d.ts.map +1 -0
  47. package/dist/backend/backend/src/services/project-tickets/ticket-autopilot.service.js +626 -0
  48. package/dist/backend/backend/src/services/project-tickets/ticket-autopilot.service.js.map +1 -0
  49. package/dist/backend/backend/src/services/reconciler/reconcile-rules.d.ts +37 -3
  50. package/dist/backend/backend/src/services/reconciler/reconcile-rules.d.ts.map +1 -1
  51. package/dist/backend/backend/src/services/reconciler/reconcile-rules.js +57 -6
  52. package/dist/backend/backend/src/services/reconciler/reconcile-rules.js.map +1 -1
  53. package/dist/backend/backend/src/services/reconciler/reconciler-data-provider.d.ts +21 -1
  54. package/dist/backend/backend/src/services/reconciler/reconciler-data-provider.d.ts.map +1 -1
  55. package/dist/backend/backend/src/services/reconciler/reconciler-data-provider.js +62 -4
  56. package/dist/backend/backend/src/services/reconciler/reconciler-data-provider.js.map +1 -1
  57. package/dist/backend/backend/src/services/reconciler/reconciler.service.d.ts +6 -0
  58. package/dist/backend/backend/src/services/reconciler/reconciler.service.d.ts.map +1 -1
  59. package/dist/backend/backend/src/services/reconciler/reconciler.service.js +9 -5
  60. package/dist/backend/backend/src/services/reconciler/reconciler.service.js.map +1 -1
  61. package/dist/backend/backend/src/services/task-pool/claim.service.d.ts +23 -3
  62. package/dist/backend/backend/src/services/task-pool/claim.service.d.ts.map +1 -1
  63. package/dist/backend/backend/src/services/task-pool/claim.service.js +52 -11
  64. package/dist/backend/backend/src/services/task-pool/claim.service.js.map +1 -1
  65. package/dist/backend/backend/src/services/task-pool/task-pool.service.d.ts +42 -0
  66. package/dist/backend/backend/src/services/task-pool/task-pool.service.d.ts.map +1 -1
  67. package/dist/backend/backend/src/services/task-pool/task-pool.service.js +82 -1
  68. package/dist/backend/backend/src/services/task-pool/task-pool.service.js.map +1 -1
  69. package/dist/backend/backend/src/services/task-pool/ticket-claim-policy.d.ts +19 -0
  70. package/dist/backend/backend/src/services/task-pool/ticket-claim-policy.d.ts.map +1 -1
  71. package/dist/backend/backend/src/services/task-pool/ticket-claim-policy.js +33 -0
  72. package/dist/backend/backend/src/services/task-pool/ticket-claim-policy.js.map +1 -1
  73. package/dist/backend/backend/src/services/v3/agent-auto-claim.service.d.ts +28 -0
  74. package/dist/backend/backend/src/services/v3/agent-auto-claim.service.d.ts.map +1 -1
  75. package/dist/backend/backend/src/services/v3/agent-auto-claim.service.js +74 -29
  76. package/dist/backend/backend/src/services/v3/agent-auto-claim.service.js.map +1 -1
  77. package/dist/backend/backend/src/services/v3/workitem-dispatch.subscriber.d.ts.map +1 -1
  78. package/dist/backend/backend/src/services/v3/workitem-dispatch.subscriber.js +3 -2
  79. package/dist/backend/backend/src/services/v3/workitem-dispatch.subscriber.js.map +1 -1
  80. package/dist/backend/backend/src/types/index.d.ts +6 -0
  81. package/dist/backend/backend/src/types/index.d.ts.map +1 -1
  82. package/dist/backend/backend/src/types/index.js.map +1 -1
  83. package/dist/backend/backend/src/types/ticket-autopilot.types.d.ts +73 -0
  84. package/dist/backend/backend/src/types/ticket-autopilot.types.d.ts.map +1 -0
  85. package/dist/backend/backend/src/types/ticket-autopilot.types.js +88 -0
  86. package/dist/backend/backend/src/types/ticket-autopilot.types.js.map +1 -0
  87. package/dist/backend/backend/src/types/v2/claim.types.d.ts +27 -0
  88. package/dist/backend/backend/src/types/v2/claim.types.d.ts.map +1 -1
  89. package/dist/backend/backend/src/types/v2/claim.types.js +37 -0
  90. package/dist/backend/backend/src/types/v2/claim.types.js.map +1 -1
  91. package/dist/backend/backend/src/types/v2/work-item.types.d.ts +1 -1
  92. package/dist/backend/backend/src/types/v2/work-item.types.d.ts.map +1 -1
  93. package/dist/backend/backend/src/types/v2/work-item.types.js +1 -0
  94. package/dist/backend/backend/src/types/v2/work-item.types.js.map +1 -1
  95. package/dist/backend/build-info.json +2 -2
  96. package/dist/cli/backend/src/constants.d.ts +84 -1
  97. package/dist/cli/backend/src/constants.d.ts.map +1 -1
  98. package/dist/cli/backend/src/constants.js +105 -16
  99. package/dist/cli/backend/src/constants.js.map +1 -1
  100. package/dist/cli/backend/src/models/Project.d.ts +3 -0
  101. package/dist/cli/backend/src/models/Project.d.ts.map +1 -1
  102. package/dist/cli/backend/src/models/Project.js +7 -0
  103. package/dist/cli/backend/src/models/Project.js.map +1 -1
  104. package/dist/cli/backend/src/services/agent/oauth-relogin-monitor.service.d.ts +66 -13
  105. package/dist/cli/backend/src/services/agent/oauth-relogin-monitor.service.d.ts.map +1 -1
  106. package/dist/cli/backend/src/services/agent/oauth-relogin-monitor.service.js +161 -32
  107. package/dist/cli/backend/src/services/agent/oauth-relogin-monitor.service.js.map +1 -1
  108. package/dist/cli/backend/src/services/task-pool/claim.service.d.ts +23 -3
  109. package/dist/cli/backend/src/services/task-pool/claim.service.d.ts.map +1 -1
  110. package/dist/cli/backend/src/services/task-pool/claim.service.js +52 -11
  111. package/dist/cli/backend/src/services/task-pool/claim.service.js.map +1 -1
  112. package/dist/cli/backend/src/services/task-pool/task-pool.service.d.ts +42 -0
  113. package/dist/cli/backend/src/services/task-pool/task-pool.service.d.ts.map +1 -1
  114. package/dist/cli/backend/src/services/task-pool/task-pool.service.js +82 -1
  115. package/dist/cli/backend/src/services/task-pool/task-pool.service.js.map +1 -1
  116. package/dist/cli/backend/src/services/task-pool/ticket-claim-policy.d.ts +19 -0
  117. package/dist/cli/backend/src/services/task-pool/ticket-claim-policy.d.ts.map +1 -1
  118. package/dist/cli/backend/src/services/task-pool/ticket-claim-policy.js +33 -0
  119. package/dist/cli/backend/src/services/task-pool/ticket-claim-policy.js.map +1 -1
  120. package/dist/cli/backend/src/types/index.d.ts +6 -0
  121. package/dist/cli/backend/src/types/index.d.ts.map +1 -1
  122. package/dist/cli/backend/src/types/index.js.map +1 -1
  123. package/dist/cli/backend/src/types/ticket-autopilot.types.d.ts +73 -0
  124. package/dist/cli/backend/src/types/ticket-autopilot.types.d.ts.map +1 -0
  125. package/dist/cli/backend/src/types/ticket-autopilot.types.js +88 -0
  126. package/dist/cli/backend/src/types/ticket-autopilot.types.js.map +1 -0
  127. package/dist/cli/backend/src/types/v2/claim.types.d.ts +27 -0
  128. package/dist/cli/backend/src/types/v2/claim.types.d.ts.map +1 -1
  129. package/dist/cli/backend/src/types/v2/claim.types.js +37 -0
  130. package/dist/cli/backend/src/types/v2/claim.types.js.map +1 -1
  131. package/dist/cli/backend/src/types/v2/work-item.types.d.ts +1 -1
  132. package/dist/cli/backend/src/types/v2/work-item.types.d.ts.map +1 -1
  133. package/dist/cli/backend/src/types/v2/work-item.types.js +1 -0
  134. package/dist/cli/backend/src/types/v2/work-item.types.js.map +1 -1
  135. package/frontend/dist/assets/{index-8f30aa74.js → index-64c8a616.js} +2 -2
  136. package/frontend/dist/index.html +1 -1
  137. package/package.json +1 -1
@@ -0,0 +1,223 @@
1
+ /**
2
+ * Ticket autopilot — the pure decisions (specs/2026-09-30-ticket-autopilot.md).
3
+ *
4
+ * Everything here is a function of its inputs (tickets, teams, clock, the
5
+ * autopilot's own bookkeeping), so the rules can be unit-tested without a
6
+ * pool, a filesystem or timers:
7
+ * - which tickets need the driver's triage ({@link selectTriageCandidates});
8
+ * - whether to wake the driver now ({@link decideTriage});
9
+ * - whether to send the owner the batched questions ({@link decideOwnerQuestions})
10
+ * or the evening digest ({@link decideDigest}).
11
+ *
12
+ * @module services/project-tickets/ticket-autopilot-decision
13
+ */
14
+ import { type ProjectTicket } from '../../types/project-ticket.types.js';
15
+ import type { Team, TeamMember } from '../../types/index.js';
16
+ /** Why a ticket is put in front of the driver. */
17
+ export type TriageReason = 'backlog' | 'ready_no_taker' | 'ready_stale';
18
+ /** One ticket for the triage brief. */
19
+ export interface TriageCandidate {
20
+ ticket: ProjectTicket;
21
+ reason: TriageReason;
22
+ /** Filed by a team member (not the owner, a lead or the orchestrator): review before it becomes ready */
23
+ workerCreated: boolean;
24
+ }
25
+ /** When a ticket was last put in a triage brief, and how it looked then. */
26
+ export interface ListedTicket {
27
+ /** Ticket `updatedAt` at the time it was listed */
28
+ updatedAt: string;
29
+ /** When it was listed (epoch ms) */
30
+ at: number;
31
+ }
32
+ /** Inputs of {@link selectTriageCandidates}. */
33
+ export interface SelectTriageInput {
34
+ tickets: ProjectTicket[];
35
+ /** Non-archived teams working on the project */
36
+ teams: Team[];
37
+ /** Clock (epoch ms) */
38
+ now: number;
39
+ /** Tickets listed in earlier triage briefs (by ticket id) */
40
+ listed?: Record<string, ListedTicket>;
41
+ }
42
+ /** Output of {@link selectTriageCandidates}. */
43
+ export interface TriageSelection {
44
+ /** At most {@link TICKET_AUTOPILOT_CONSTANTS.TRIAGE_MAX_TICKETS}, highest priority then oldest first */
45
+ candidates: TriageCandidate[];
46
+ /** Tickets that need triage but did not fit in this brief */
47
+ more: number;
48
+ }
49
+ /** What set off the evaluation. */
50
+ export type TriageTrigger = 'tick' | 'member_idle';
51
+ /** Inputs of {@link decideTriage}. */
52
+ export interface TriageDecisionInput {
53
+ enabled: boolean;
54
+ /** Resolved driver session, or null when the project has no lead */
55
+ driver: string | null;
56
+ trigger: TriageTrigger;
57
+ /** Clock (epoch ms) */
58
+ now: number;
59
+ /** Tickets that need triage (after de-duplication) */
60
+ candidateCount: number;
61
+ /** A triage WorkItem of this project is still live */
62
+ liveTriage: boolean;
63
+ /** When the last triage item was created (epoch ms) */
64
+ lastTriageAt?: number;
65
+ /** A member of the project's teams (or the driver) is idle */
66
+ anyoneIdle: boolean;
67
+ /** Spent today (USD) by the project's team agents */
68
+ spentTodayUsd: number;
69
+ /** Daily budget (USD) */
70
+ dailyBudgetUsd: number;
71
+ }
72
+ /** Why the driver is not woken. */
73
+ export type TriageSkipReason = 'off' | 'no_driver' | 'budget_reached' | 'triage_in_flight' | 'nothing_to_triage' | 'nobody_idle' | 'too_soon';
74
+ /** Outcome of {@link decideTriage}. */
75
+ export type TriageDecision = {
76
+ action: 'triage';
77
+ } | {
78
+ action: 'skip';
79
+ reason: TriageSkipReason;
80
+ };
81
+ /**
82
+ * Whether a ticket carries the needs-owner label.
83
+ *
84
+ * @param ticket - Ticket
85
+ * @returns True when it waits on the owner
86
+ */
87
+ export declare function hasNeedsOwnerLabel(ticket: Pick<ProjectTicket, 'labels'>): boolean;
88
+ /**
89
+ * The one-line question for the owner recorded on a ticket (the latest Log
90
+ * line written by `ask-owner`), or null.
91
+ *
92
+ * @param ticket - Ticket
93
+ * @returns The question text, or null
94
+ */
95
+ export declare function readOwnerQuestion(ticket: Pick<ProjectTicket, 'log'>): string | null;
96
+ /**
97
+ * Whether a ticket was filed by a worker — an agent that is neither the
98
+ * orchestrator nor a lead of one of the project's teams. Owner, request,
99
+ * migration and lead/orc tickets are not worker-created.
100
+ *
101
+ * @param ticket - Ticket
102
+ * @param teams - The project's teams
103
+ * @returns True for a worker-created ticket
104
+ */
105
+ export declare function isWorkerCreated(ticket: Pick<ProjectTicket, 'source'>, teams: Team[]): boolean;
106
+ /**
107
+ * Whether a member is running and idle.
108
+ *
109
+ * @param member - Team member
110
+ * @returns True when active (or started) and not working
111
+ */
112
+ export declare function isMemberIdle(member: Pick<TeamMember, 'agentStatus' | 'workingStatus'>): boolean;
113
+ /**
114
+ * Whether anyone could ever auto-claim a `ready` ticket: a member of an
115
+ * eligible team (the ticket's `team`, else any project team) who is not a
116
+ * lead — or a lead who is the only member of its team. Mirrors the
117
+ * AutoClaim rule (specs/2026-09-28-project-tickets.md §5).
118
+ *
119
+ * @param ticket - Ticket
120
+ * @param teams - The project's teams
121
+ * @returns True when some member could take it
122
+ */
123
+ export declare function hasPossibleTaker(ticket: Pick<ProjectTicket, 'team'>, teams: Team[]): boolean;
124
+ /**
125
+ * Tickets the driver should triage now, in brief order.
126
+ *
127
+ * - `backlog` tickets not waiting on the owner (no `needs-owner` label);
128
+ * - `ready` tickets nobody can take (no eligible claimer), or untouched for
129
+ * {@link TICKET_AUTOPILOT_CONSTANTS.READY_STALE_MS};
130
+ * - minus tickets already listed in a brief and unchanged since, until
131
+ * {@link TICKET_AUTOPILOT_CONSTANTS.TRIAGE_RELIST_AFTER_MS} has passed (a
132
+ * ticket the driver chose to leave is not re-sent every half hour).
133
+ *
134
+ * @param input - Tickets, teams, clock, earlier listings
135
+ * @returns Candidates (capped) and how many more are waiting
136
+ */
137
+ export declare function selectTriageCandidates(input: SelectTriageInput): TriageSelection;
138
+ /**
139
+ * Whether to wake the driver with a triage item now. Checked in order:
140
+ * switch off → no driver → budget reached → a triage already live → nothing
141
+ * to triage → nobody idle → too soon since the last one (30 min on the
142
+ * periodic tick, {@link TICKET_AUTOPILOT_CONSTANTS.IDLE_TRIGGER_MIN_INTERVAL_MS}
143
+ * when a member just went idle with nothing ready).
144
+ *
145
+ * @param input - State of the project
146
+ * @returns `triage`, or `skip` with the reason
147
+ */
148
+ export declare function decideTriage(input: TriageDecisionInput): TriageDecision;
149
+ /** A question waiting on the owner. */
150
+ export interface PendingOwnerQuestion {
151
+ /** Stable key: project + ticket + question text */
152
+ key: string;
153
+ priority: string;
154
+ }
155
+ /** Inputs of {@link decideOwnerQuestions}. */
156
+ export interface OwnerQuestionsInput {
157
+ pending: PendingOwnerQuestion[];
158
+ /** Keys already sent to the owner */
159
+ sentKeys: ReadonlySet<string>;
160
+ /** When the last batch went out (epoch ms) */
161
+ lastSentAt?: number;
162
+ now: number;
163
+ }
164
+ /**
165
+ * Whether to send the batched owner questions now: only when at least one
166
+ * question is new, and then at most every
167
+ * {@link TICKET_AUTOPILOT_CONSTANTS.QUESTIONS_MIN_INTERVAL_MS} — unless a new
168
+ * one is urgent (P0), which goes out at once.
169
+ *
170
+ * @param input - Pending questions, what was sent, clock
171
+ * @returns `send` and why
172
+ */
173
+ export declare function decideOwnerQuestions(input: OwnerQuestionsInput): {
174
+ send: boolean;
175
+ reason: 'nothing_new' | 'urgent' | 'due' | 'too_soon';
176
+ };
177
+ /** Inputs of {@link decideDigest}. */
178
+ export interface DigestDecisionInput {
179
+ now: Date;
180
+ /** Local date (YYYY-MM-DD) of the last digest sent */
181
+ lastSentDate?: string;
182
+ /** When the last digest went out (epoch ms) */
183
+ lastSentAt?: number;
184
+ /** Latest `updatedAt` (epoch ms) over the tickets of the enabled projects */
185
+ latestTicketChangeAt: number;
186
+ }
187
+ /**
188
+ * Whether to send the daily digest now: once per local day, at or after
189
+ * {@link TICKET_AUTOPILOT_CONSTANTS.DIGEST_HOUR_LOCAL}, and only when some
190
+ * ticket changed since the previous digest (or in the last day, for the
191
+ * first one).
192
+ *
193
+ * @param input - Clock, last digest, latest ticket change
194
+ * @returns `send` and why
195
+ */
196
+ export declare function decideDigest(input: DigestDecisionInput): {
197
+ send: boolean;
198
+ reason: 'not_yet' | 'already_sent' | 'nothing_changed' | 'due';
199
+ };
200
+ /**
201
+ * Local midnight of a moment's day.
202
+ *
203
+ * @param now - Moment
204
+ * @returns 00:00 local time of that day
205
+ */
206
+ export declare function localMidnight(now: Date): Date;
207
+ /**
208
+ * Local calendar date key.
209
+ *
210
+ * @param now - Moment
211
+ * @returns `YYYY-MM-DD` in local time
212
+ */
213
+ export declare function localDateKey(now: Date): string;
214
+ /**
215
+ * Tickets in progress per assignee.
216
+ *
217
+ * @param tickets - Tickets of a project
218
+ * @returns Session → count of `in_progress` tickets
219
+ */
220
+ export declare function inFlightByAssignee(tickets: ProjectTicket[]): Map<string, number>;
221
+ /** Statuses that count as "open" for owner questions (a closed ticket asks nothing). */
222
+ export declare const OPEN_TICKET_STATUSES: ReadonlySet<string>;
223
+ //# sourceMappingURL=ticket-autopilot-decision.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ticket-autopilot-decision.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/project-tickets/ticket-autopilot-decision.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,240 @@
1
+ /**
2
+ * Ticket autopilot — the pure decisions (specs/2026-09-30-ticket-autopilot.md).
3
+ *
4
+ * Everything here is a function of its inputs (tickets, teams, clock, the
5
+ * autopilot's own bookkeeping), so the rules can be unit-tested without a
6
+ * pool, a filesystem or timers:
7
+ * - which tickets need the driver's triage ({@link selectTriageCandidates});
8
+ * - whether to wake the driver now ({@link decideTriage});
9
+ * - whether to send the owner the batched questions ({@link decideOwnerQuestions})
10
+ * or the evening digest ({@link decideDigest}).
11
+ *
12
+ * @module services/project-tickets/ticket-autopilot-decision
13
+ */
14
+ import { ORCHESTRATOR_SESSION_NAME, PROJECT_TICKET_CONSTANTS, TICKET_AUTOPILOT_CONSTANTS } from '../../constants.js';
15
+ import { projectTicketPriorityRank } from '../../types/project-ticket.types.js';
16
+ import { isTeamLead } from './project-ticket-workflow.service.js';
17
+ /**
18
+ * Whether a ticket carries the needs-owner label.
19
+ *
20
+ * @param ticket - Ticket
21
+ * @returns True when it waits on the owner
22
+ */
23
+ export function hasNeedsOwnerLabel(ticket) {
24
+ return ticket.labels.includes(TICKET_AUTOPILOT_CONSTANTS.NEEDS_OWNER_LABEL);
25
+ }
26
+ /**
27
+ * The one-line question for the owner recorded on a ticket (the latest Log
28
+ * line written by `ask-owner`), or null.
29
+ *
30
+ * @param ticket - Ticket
31
+ * @returns The question text, or null
32
+ */
33
+ export function readOwnerQuestion(ticket) {
34
+ const prefix = TICKET_AUTOPILOT_CONSTANTS.OWNER_QUESTION_LOG_PREFIX;
35
+ for (let i = ticket.log.length - 1; i >= 0; i--) {
36
+ const at = ticket.log[i].indexOf(prefix);
37
+ if (at >= 0) {
38
+ const text = ticket.log[i].slice(at + prefix.length).trim();
39
+ if (text)
40
+ return text;
41
+ }
42
+ }
43
+ return null;
44
+ }
45
+ /**
46
+ * Whether a ticket was filed by a worker — an agent that is neither the
47
+ * orchestrator nor a lead of one of the project's teams. Owner, request,
48
+ * migration and lead/orc tickets are not worker-created.
49
+ *
50
+ * @param ticket - Ticket
51
+ * @param teams - The project's teams
52
+ * @returns True for a worker-created ticket
53
+ */
54
+ export function isWorkerCreated(ticket, teams) {
55
+ const source = ticket.source ?? '';
56
+ if (!source.startsWith('agent:'))
57
+ return false;
58
+ const session = source.slice('agent:'.length).trim();
59
+ if (!session || session === ORCHESTRATOR_SESSION_NAME)
60
+ return false;
61
+ for (const team of teams) {
62
+ for (const m of team.members ?? []) {
63
+ if ((m.sessionName === session || m.agentId === session) && isTeamLead(team, m))
64
+ return false;
65
+ }
66
+ }
67
+ return true;
68
+ }
69
+ /**
70
+ * Whether a member is running and idle.
71
+ *
72
+ * @param member - Team member
73
+ * @returns True when active (or started) and not working
74
+ */
75
+ export function isMemberIdle(member) {
76
+ return (member.agentStatus === 'active' || member.agentStatus === 'started') && member.workingStatus === 'idle';
77
+ }
78
+ /**
79
+ * Whether anyone could ever auto-claim a `ready` ticket: a member of an
80
+ * eligible team (the ticket's `team`, else any project team) who is not a
81
+ * lead — or a lead who is the only member of its team. Mirrors the
82
+ * AutoClaim rule (specs/2026-09-28-project-tickets.md §5).
83
+ *
84
+ * @param ticket - Ticket
85
+ * @param teams - The project's teams
86
+ * @returns True when some member could take it
87
+ */
88
+ export function hasPossibleTaker(ticket, teams) {
89
+ return teams
90
+ .filter((t) => !ticket.team || t.id === ticket.team)
91
+ .some((t) => {
92
+ const members = t.members ?? [];
93
+ return members.some((m) => !isTeamLead(t, m) || members.length === 1);
94
+ });
95
+ }
96
+ /**
97
+ * Tickets the driver should triage now, in brief order.
98
+ *
99
+ * - `backlog` tickets not waiting on the owner (no `needs-owner` label);
100
+ * - `ready` tickets nobody can take (no eligible claimer), or untouched for
101
+ * {@link TICKET_AUTOPILOT_CONSTANTS.READY_STALE_MS};
102
+ * - minus tickets already listed in a brief and unchanged since, until
103
+ * {@link TICKET_AUTOPILOT_CONSTANTS.TRIAGE_RELIST_AFTER_MS} has passed (a
104
+ * ticket the driver chose to leave is not re-sent every half hour).
105
+ *
106
+ * @param input - Tickets, teams, clock, earlier listings
107
+ * @returns Candidates (capped) and how many more are waiting
108
+ */
109
+ export function selectTriageCandidates(input) {
110
+ const listed = input.listed ?? {};
111
+ const all = [];
112
+ for (const ticket of input.tickets) {
113
+ let reason = null;
114
+ if (ticket.status === 'backlog' && !hasNeedsOwnerLabel(ticket)) {
115
+ reason = 'backlog';
116
+ }
117
+ else if (ticket.status === 'ready' && !hasNeedsOwnerLabel(ticket)) {
118
+ if (!hasPossibleTaker(ticket, input.teams))
119
+ reason = 'ready_no_taker';
120
+ else if (input.now - (Date.parse(ticket.updatedAt) || input.now) >= TICKET_AUTOPILOT_CONSTANTS.READY_STALE_MS)
121
+ reason = 'ready_stale';
122
+ }
123
+ if (!reason)
124
+ continue;
125
+ const seen = listed[ticket.id];
126
+ if (seen && seen.updatedAt === ticket.updatedAt && input.now - seen.at < TICKET_AUTOPILOT_CONSTANTS.TRIAGE_RELIST_AFTER_MS)
127
+ continue;
128
+ all.push({ ticket, reason, workerCreated: isWorkerCreated(ticket, input.teams) });
129
+ }
130
+ all.sort((a, b) => projectTicketPriorityRank(a.ticket.priority) - projectTicketPriorityRank(b.ticket.priority) ||
131
+ (Date.parse(a.ticket.createdAt) || 0) - (Date.parse(b.ticket.createdAt) || 0) ||
132
+ a.ticket.id.localeCompare(b.ticket.id));
133
+ const max = TICKET_AUTOPILOT_CONSTANTS.TRIAGE_MAX_TICKETS;
134
+ return { candidates: all.slice(0, max), more: Math.max(0, all.length - max) };
135
+ }
136
+ /**
137
+ * Whether to wake the driver with a triage item now. Checked in order:
138
+ * switch off → no driver → budget reached → a triage already live → nothing
139
+ * to triage → nobody idle → too soon since the last one (30 min on the
140
+ * periodic tick, {@link TICKET_AUTOPILOT_CONSTANTS.IDLE_TRIGGER_MIN_INTERVAL_MS}
141
+ * when a member just went idle with nothing ready).
142
+ *
143
+ * @param input - State of the project
144
+ * @returns `triage`, or `skip` with the reason
145
+ */
146
+ export function decideTriage(input) {
147
+ if (!input.enabled)
148
+ return { action: 'skip', reason: 'off' };
149
+ if (!input.driver)
150
+ return { action: 'skip', reason: 'no_driver' };
151
+ if (input.spentTodayUsd >= input.dailyBudgetUsd)
152
+ return { action: 'skip', reason: 'budget_reached' };
153
+ if (input.liveTriage)
154
+ return { action: 'skip', reason: 'triage_in_flight' };
155
+ if (input.candidateCount === 0)
156
+ return { action: 'skip', reason: 'nothing_to_triage' };
157
+ if (!input.anyoneIdle)
158
+ return { action: 'skip', reason: 'nobody_idle' };
159
+ const gap = input.trigger === 'member_idle' ? TICKET_AUTOPILOT_CONSTANTS.IDLE_TRIGGER_MIN_INTERVAL_MS : TICKET_AUTOPILOT_CONSTANTS.TRIAGE_MIN_INTERVAL_MS;
160
+ if (input.lastTriageAt !== undefined && input.now - input.lastTriageAt < gap)
161
+ return { action: 'skip', reason: 'too_soon' };
162
+ return { action: 'triage' };
163
+ }
164
+ /**
165
+ * Whether to send the batched owner questions now: only when at least one
166
+ * question is new, and then at most every
167
+ * {@link TICKET_AUTOPILOT_CONSTANTS.QUESTIONS_MIN_INTERVAL_MS} — unless a new
168
+ * one is urgent (P0), which goes out at once.
169
+ *
170
+ * @param input - Pending questions, what was sent, clock
171
+ * @returns `send` and why
172
+ */
173
+ export function decideOwnerQuestions(input) {
174
+ const fresh = input.pending.filter((q) => !input.sentKeys.has(q.key));
175
+ if (fresh.length === 0)
176
+ return { send: false, reason: 'nothing_new' };
177
+ if (fresh.some((q) => q.priority === TICKET_AUTOPILOT_CONSTANTS.URGENT_PRIORITY))
178
+ return { send: true, reason: 'urgent' };
179
+ if (input.lastSentAt === undefined || input.now - input.lastSentAt >= TICKET_AUTOPILOT_CONSTANTS.QUESTIONS_MIN_INTERVAL_MS) {
180
+ return { send: true, reason: 'due' };
181
+ }
182
+ return { send: false, reason: 'too_soon' };
183
+ }
184
+ /**
185
+ * Whether to send the daily digest now: once per local day, at or after
186
+ * {@link TICKET_AUTOPILOT_CONSTANTS.DIGEST_HOUR_LOCAL}, and only when some
187
+ * ticket changed since the previous digest (or in the last day, for the
188
+ * first one).
189
+ *
190
+ * @param input - Clock, last digest, latest ticket change
191
+ * @returns `send` and why
192
+ */
193
+ export function decideDigest(input) {
194
+ if (input.now.getHours() < TICKET_AUTOPILOT_CONSTANTS.DIGEST_HOUR_LOCAL)
195
+ return { send: false, reason: 'not_yet' };
196
+ if (input.lastSentDate === localDateKey(input.now))
197
+ return { send: false, reason: 'already_sent' };
198
+ const since = input.lastSentAt ?? input.now.getTime() - 24 * 60 * 60 * 1000;
199
+ if (!(input.latestTicketChangeAt > since))
200
+ return { send: false, reason: 'nothing_changed' };
201
+ return { send: true, reason: 'due' };
202
+ }
203
+ /**
204
+ * Local midnight of a moment's day.
205
+ *
206
+ * @param now - Moment
207
+ * @returns 00:00 local time of that day
208
+ */
209
+ export function localMidnight(now) {
210
+ const d = new Date(now.getTime());
211
+ d.setHours(0, 0, 0, 0);
212
+ return d;
213
+ }
214
+ /**
215
+ * Local calendar date key.
216
+ *
217
+ * @param now - Moment
218
+ * @returns `YYYY-MM-DD` in local time
219
+ */
220
+ export function localDateKey(now) {
221
+ const pad = (n) => String(n).padStart(2, '0');
222
+ return `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}`;
223
+ }
224
+ /**
225
+ * Tickets in progress per assignee.
226
+ *
227
+ * @param tickets - Tickets of a project
228
+ * @returns Session → count of `in_progress` tickets
229
+ */
230
+ export function inFlightByAssignee(tickets) {
231
+ const out = new Map();
232
+ for (const t of tickets) {
233
+ if (t.status === 'in_progress' && t.assignee)
234
+ out.set(t.assignee, (out.get(t.assignee) ?? 0) + 1);
235
+ }
236
+ return out;
237
+ }
238
+ /** Statuses that count as "open" for owner questions (a closed ticket asks nothing). */
239
+ export const OPEN_TICKET_STATUSES = new Set(PROJECT_TICKET_CONSTANTS.STATUSES.filter((s) => s !== 'done' && s !== 'cancelled'));
240
+ //# sourceMappingURL=ticket-autopilot-decision.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ticket-autopilot-decision.js","sourceRoot":"","sources":["../../../../../../backend/src/services/project-tickets/ticket-autopilot-decision.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Ticket autopilot — the texts (specs/2026-09-30-ticket-autopilot.md §3, §5):
3
+ * the driver's triage brief, the owner's batched questions and the evening
4
+ * digest. Pure functions; the owner-facing texts are phone-sized and carry no
5
+ * harness mechanics (no WorkItem ids, claims or pool states).
6
+ *
7
+ * @module services/project-tickets/ticket-autopilot-messages
8
+ */
9
+ import type { ProjectTicket } from '../../types/project-ticket.types.js';
10
+ import type { TriageCandidate } from './ticket-autopilot-decision.js';
11
+ /**
12
+ * Actions that need the owner's explicit OK even with the autopilot on.
13
+ * Spelled out in the triage brief and in the team-leader prompt.
14
+ */
15
+ export declare const TICKET_AUTOPILOT_BOUNDARIES: readonly string[];
16
+ /** A team member as the triage brief shows it. */
17
+ export interface TriageBriefMember {
18
+ session: string;
19
+ /** Display name, when different from the session */
20
+ name?: string;
21
+ role?: string;
22
+ idle: boolean;
23
+ /** Tickets in progress for this member */
24
+ inFlight: number;
25
+ }
26
+ /** Inputs of {@link buildTriageBrief}. */
27
+ export interface TriageBriefInput {
28
+ project: {
29
+ id: string;
30
+ name: string;
31
+ };
32
+ candidates: TriageCandidate[];
33
+ /** Tickets that need triage but did not fit */
34
+ more: number;
35
+ members: TriageBriefMember[];
36
+ maxInFlightPerMember: number;
37
+ /** Clock (epoch ms), for ticket ages */
38
+ now: number;
39
+ }
40
+ /**
41
+ * Human age of a timestamp ("3h", "2d").
42
+ *
43
+ * @param iso - ISO time
44
+ * @param now - Clock (epoch ms)
45
+ * @returns Short age, or "?" when unreadable
46
+ */
47
+ export declare function formatAge(iso: string, now: number): string;
48
+ /**
49
+ * The triage brief the driver receives (WorkItem `briefMarkdown`).
50
+ *
51
+ * @param input - Project, tickets, team, limits, clock
52
+ * @returns Markdown brief
53
+ */
54
+ export declare function buildTriageBrief(input: TriageBriefInput): string;
55
+ /** A question for the owner. */
56
+ export interface OwnerQuestionItem {
57
+ projectName: string;
58
+ ticketId: string;
59
+ title: string;
60
+ question: string;
61
+ }
62
+ /**
63
+ * The one batched message with every open owner question — numbered, short,
64
+ * answerable with a tap or a word.
65
+ *
66
+ * @param items - Questions (already ordered)
67
+ * @returns Message text
68
+ */
69
+ export declare function buildOwnerQuestionsMessage(items: OwnerQuestionItem[]): string;
70
+ /** One project's section of the digest. */
71
+ export interface DigestProject {
72
+ name: string;
73
+ doneToday: ProjectTicket[];
74
+ inProgress: ProjectTicket[];
75
+ waitingOnOwner: ProjectTicket[];
76
+ }
77
+ /**
78
+ * The evening digest: per project, done today / in progress / waiting on the
79
+ * owner. Projects with nothing to report are left out.
80
+ *
81
+ * @param projects - Per-project sections
82
+ * @returns Message text, or null when there is nothing to say
83
+ */
84
+ export declare function buildDigestMessage(projects: DigestProject[]): string | null;
85
+ /**
86
+ * The once-a-day notice that the autopilot paused on its budget.
87
+ *
88
+ * @param projectName - Project
89
+ * @param spentUsd - Spent today
90
+ * @param budgetUsd - Daily budget
91
+ * @returns Message text
92
+ */
93
+ export declare function buildBudgetPausedMessage(projectName: string, spentUsd: number, budgetUsd: number): string;
94
+ //# sourceMappingURL=ticket-autopilot-messages.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ticket-autopilot-messages.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/project-tickets/ticket-autopilot-messages.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qCAAqC,CAAC;AACzE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AAEtE;;;GAGG;AACH,eAAO,MAAM,2BAA2B,EAAE,SAAS,MAAM,EAKxD,CAAC;AAEF,kDAAkD;AAClD,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,oDAAoD;IACpD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,OAAO,CAAC;IACd,0CAA0C;IAC1C,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,0CAA0C;AAC1C,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IACtC,UAAU,EAAE,eAAe,EAAE,CAAC;IAC9B,+CAA+C;IAC/C,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,iBAAiB,EAAE,CAAC;IAC7B,oBAAoB,EAAE,MAAM,CAAC;IAC7B,wCAAwC;IACxC,GAAG,EAAE,MAAM,CAAC;CACb;AAED;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAQ1D;AA0BD;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,gBAAgB,GAAG,MAAM,CAuDhE;AAED,gCAAgC;AAChC,MAAM,WAAW,iBAAiB;IAChC,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;GAMG;AACH,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,iBAAiB,EAAE,GAAG,MAAM,CAU7E;AAED,2CAA2C;AAC3C,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,aAAa,EAAE,CAAC;IAC3B,UAAU,EAAE,aAAa,EAAE,CAAC;IAC5B,cAAc,EAAE,aAAa,EAAE,CAAC;CACjC;AAoBD;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,aAAa,EAAE,GAAG,MAAM,GAAG,IAAI,CAa3E;AAED;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,CAAC,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAEzG"}