crewly 1.20.160 → 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.
- package/config/roles/orchestrator/prompt.md +2 -0
- package/config/roles/team-leader/prompt.md +12 -0
- package/config/roles/team-leader/tl-addon.md +12 -0
- package/config/skills/agent/core/project-tickets/SKILL.md +32 -1
- package/config/skills/agent/core/project-tickets/execute.sh +56 -2
- package/config/skills/agent/core/project-tickets/execute.test.sh +22 -0
- package/dist/backend/backend/src/constants.d.ts +125 -1
- package/dist/backend/backend/src/constants.d.ts.map +1 -1
- package/dist/backend/backend/src/constants.js +146 -16
- package/dist/backend/backend/src/constants.js.map +1 -1
- package/dist/backend/backend/src/controllers/browser/browser.controller.d.ts +27 -0
- package/dist/backend/backend/src/controllers/browser/browser.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/browser/browser.controller.js +203 -1
- package/dist/backend/backend/src/controllers/browser/browser.controller.js.map +1 -1
- package/dist/backend/backend/src/controllers/browser/browser.routes.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/browser/browser.routes.js +4 -1
- package/dist/backend/backend/src/controllers/browser/browser.routes.js.map +1 -1
- package/dist/backend/backend/src/controllers/project-tickets/project-tickets.controller.d.ts +43 -0
- package/dist/backend/backend/src/controllers/project-tickets/project-tickets.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/project-tickets/project-tickets.controller.js +85 -0
- package/dist/backend/backend/src/controllers/project-tickets/project-tickets.controller.js.map +1 -1
- package/dist/backend/backend/src/controllers/project-tickets/project-tickets.routes.d.ts +12 -0
- package/dist/backend/backend/src/controllers/project-tickets/project-tickets.routes.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/project-tickets/project-tickets.routes.js +19 -1
- package/dist/backend/backend/src/controllers/project-tickets/project-tickets.routes.js.map +1 -1
- package/dist/backend/backend/src/index.d.ts.map +1 -1
- package/dist/backend/backend/src/index.js +30 -1
- package/dist/backend/backend/src/index.js.map +1 -1
- package/dist/backend/backend/src/models/Project.d.ts +3 -0
- package/dist/backend/backend/src/models/Project.d.ts.map +1 -1
- package/dist/backend/backend/src/models/Project.js +7 -0
- package/dist/backend/backend/src/models/Project.js.map +1 -1
- package/dist/backend/backend/src/routes/api.routes.d.ts.map +1 -1
- package/dist/backend/backend/src/routes/api.routes.js +3 -1
- package/dist/backend/backend/src/routes/api.routes.js.map +1 -1
- package/dist/backend/backend/src/services/agent/oauth-relogin-monitor.service.d.ts +66 -13
- package/dist/backend/backend/src/services/agent/oauth-relogin-monitor.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/agent/oauth-relogin-monitor.service.js +161 -32
- package/dist/backend/backend/src/services/agent/oauth-relogin-monitor.service.js.map +1 -1
- package/dist/backend/backend/src/services/browser/browser-session.service.d.ts +15 -0
- package/dist/backend/backend/src/services/browser/browser-session.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/browser/browser-session.service.js +25 -0
- package/dist/backend/backend/src/services/browser/browser-session.service.js.map +1 -1
- package/dist/backend/backend/src/services/browser/owner-browser-input.d.ts +211 -0
- package/dist/backend/backend/src/services/browser/owner-browser-input.d.ts.map +1 -0
- package/dist/backend/backend/src/services/browser/owner-browser-input.js +373 -0
- package/dist/backend/backend/src/services/browser/owner-browser-input.js.map +1 -0
- package/dist/backend/backend/src/services/cloud/mobile-api-relay.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/cloud/mobile-api-relay.service.js +5 -3
- package/dist/backend/backend/src/services/cloud/mobile-api-relay.service.js.map +1 -1
- package/dist/backend/backend/src/services/project-tickets/project-ticket-workflow.service.d.ts +48 -0
- package/dist/backend/backend/src/services/project-tickets/project-ticket-workflow.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/project-tickets/project-ticket-workflow.service.js +77 -1
- package/dist/backend/backend/src/services/project-tickets/project-ticket-workflow.service.js.map +1 -1
- package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-decision.d.ts +223 -0
- package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-decision.d.ts.map +1 -0
- package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-decision.js +240 -0
- package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-decision.js.map +1 -0
- package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-messages.d.ts +94 -0
- package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-messages.d.ts.map +1 -0
- package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-messages.js +195 -0
- package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-messages.js.map +1 -0
- package/dist/backend/backend/src/services/project-tickets/ticket-autopilot.service.d.ts +286 -0
- package/dist/backend/backend/src/services/project-tickets/ticket-autopilot.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/project-tickets/ticket-autopilot.service.js +626 -0
- package/dist/backend/backend/src/services/project-tickets/ticket-autopilot.service.js.map +1 -0
- package/dist/backend/backend/src/services/reconciler/reconcile-rules.d.ts +37 -3
- package/dist/backend/backend/src/services/reconciler/reconcile-rules.d.ts.map +1 -1
- package/dist/backend/backend/src/services/reconciler/reconcile-rules.js +57 -6
- package/dist/backend/backend/src/services/reconciler/reconcile-rules.js.map +1 -1
- package/dist/backend/backend/src/services/reconciler/reconciler-data-provider.d.ts +21 -1
- package/dist/backend/backend/src/services/reconciler/reconciler-data-provider.d.ts.map +1 -1
- package/dist/backend/backend/src/services/reconciler/reconciler-data-provider.js +62 -4
- package/dist/backend/backend/src/services/reconciler/reconciler-data-provider.js.map +1 -1
- package/dist/backend/backend/src/services/reconciler/reconciler.service.d.ts +6 -0
- package/dist/backend/backend/src/services/reconciler/reconciler.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/reconciler/reconciler.service.js +9 -5
- package/dist/backend/backend/src/services/reconciler/reconciler.service.js.map +1 -1
- package/dist/backend/backend/src/services/task-pool/claim.service.d.ts +23 -3
- package/dist/backend/backend/src/services/task-pool/claim.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/task-pool/claim.service.js +52 -11
- package/dist/backend/backend/src/services/task-pool/claim.service.js.map +1 -1
- package/dist/backend/backend/src/services/task-pool/task-pool.service.d.ts +42 -0
- package/dist/backend/backend/src/services/task-pool/task-pool.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/task-pool/task-pool.service.js +82 -1
- package/dist/backend/backend/src/services/task-pool/task-pool.service.js.map +1 -1
- package/dist/backend/backend/src/services/task-pool/ticket-claim-policy.d.ts +19 -0
- package/dist/backend/backend/src/services/task-pool/ticket-claim-policy.d.ts.map +1 -1
- package/dist/backend/backend/src/services/task-pool/ticket-claim-policy.js +33 -0
- package/dist/backend/backend/src/services/task-pool/ticket-claim-policy.js.map +1 -1
- package/dist/backend/backend/src/services/v3/agent-auto-claim.service.d.ts +28 -0
- package/dist/backend/backend/src/services/v3/agent-auto-claim.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/v3/agent-auto-claim.service.js +74 -29
- package/dist/backend/backend/src/services/v3/agent-auto-claim.service.js.map +1 -1
- package/dist/backend/backend/src/services/v3/workitem-dispatch.subscriber.d.ts.map +1 -1
- package/dist/backend/backend/src/services/v3/workitem-dispatch.subscriber.js +3 -2
- package/dist/backend/backend/src/services/v3/workitem-dispatch.subscriber.js.map +1 -1
- package/dist/backend/backend/src/types/index.d.ts +6 -0
- package/dist/backend/backend/src/types/index.d.ts.map +1 -1
- package/dist/backend/backend/src/types/index.js.map +1 -1
- package/dist/backend/backend/src/types/ticket-autopilot.types.d.ts +73 -0
- package/dist/backend/backend/src/types/ticket-autopilot.types.d.ts.map +1 -0
- package/dist/backend/backend/src/types/ticket-autopilot.types.js +88 -0
- package/dist/backend/backend/src/types/ticket-autopilot.types.js.map +1 -0
- package/dist/backend/backend/src/types/v2/claim.types.d.ts +27 -0
- package/dist/backend/backend/src/types/v2/claim.types.d.ts.map +1 -1
- package/dist/backend/backend/src/types/v2/claim.types.js +37 -0
- package/dist/backend/backend/src/types/v2/claim.types.js.map +1 -1
- package/dist/backend/backend/src/types/v2/work-item.types.d.ts +1 -1
- package/dist/backend/backend/src/types/v2/work-item.types.d.ts.map +1 -1
- package/dist/backend/backend/src/types/v2/work-item.types.js +1 -0
- package/dist/backend/backend/src/types/v2/work-item.types.js.map +1 -1
- package/dist/backend/build-info.json +2 -2
- package/dist/cli/backend/src/constants.d.ts +125 -1
- package/dist/cli/backend/src/constants.d.ts.map +1 -1
- package/dist/cli/backend/src/constants.js +146 -16
- package/dist/cli/backend/src/constants.js.map +1 -1
- package/dist/cli/backend/src/models/Project.d.ts +3 -0
- package/dist/cli/backend/src/models/Project.d.ts.map +1 -1
- package/dist/cli/backend/src/models/Project.js +7 -0
- package/dist/cli/backend/src/models/Project.js.map +1 -1
- package/dist/cli/backend/src/services/agent/oauth-relogin-monitor.service.d.ts +66 -13
- package/dist/cli/backend/src/services/agent/oauth-relogin-monitor.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/agent/oauth-relogin-monitor.service.js +161 -32
- package/dist/cli/backend/src/services/agent/oauth-relogin-monitor.service.js.map +1 -1
- package/dist/cli/backend/src/services/task-pool/claim.service.d.ts +23 -3
- package/dist/cli/backend/src/services/task-pool/claim.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/task-pool/claim.service.js +52 -11
- package/dist/cli/backend/src/services/task-pool/claim.service.js.map +1 -1
- package/dist/cli/backend/src/services/task-pool/task-pool.service.d.ts +42 -0
- package/dist/cli/backend/src/services/task-pool/task-pool.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/task-pool/task-pool.service.js +82 -1
- package/dist/cli/backend/src/services/task-pool/task-pool.service.js.map +1 -1
- package/dist/cli/backend/src/services/task-pool/ticket-claim-policy.d.ts +19 -0
- package/dist/cli/backend/src/services/task-pool/ticket-claim-policy.d.ts.map +1 -1
- package/dist/cli/backend/src/services/task-pool/ticket-claim-policy.js +33 -0
- package/dist/cli/backend/src/services/task-pool/ticket-claim-policy.js.map +1 -1
- package/dist/cli/backend/src/types/index.d.ts +6 -0
- package/dist/cli/backend/src/types/index.d.ts.map +1 -1
- package/dist/cli/backend/src/types/index.js.map +1 -1
- package/dist/cli/backend/src/types/ticket-autopilot.types.d.ts +73 -0
- package/dist/cli/backend/src/types/ticket-autopilot.types.d.ts.map +1 -0
- package/dist/cli/backend/src/types/ticket-autopilot.types.js +88 -0
- package/dist/cli/backend/src/types/ticket-autopilot.types.js.map +1 -0
- package/dist/cli/backend/src/types/v2/claim.types.d.ts +27 -0
- package/dist/cli/backend/src/types/v2/claim.types.d.ts.map +1 -1
- package/dist/cli/backend/src/types/v2/claim.types.js +37 -0
- package/dist/cli/backend/src/types/v2/claim.types.js.map +1 -1
- package/dist/cli/backend/src/types/v2/work-item.types.d.ts +1 -1
- package/dist/cli/backend/src/types/v2/work-item.types.d.ts.map +1 -1
- package/dist/cli/backend/src/types/v2/work-item.types.js +1 -0
- package/dist/cli/backend/src/types/v2/work-item.types.js.map +1 -1
- package/frontend/dist/assets/index-0707e2ac.css +42 -0
- package/frontend/dist/assets/{index-f79676aa.js → index-64c8a616.js} +265 -265
- package/frontend/dist/index.html +2 -2
- package/package.json +1 -1
- package/frontend/dist/assets/index-edd6f262.css +0 -42
|
@@ -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"}
|
|
@@ -0,0 +1,195 @@
|
|
|
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 { TICKET_AUTOPILOT_CONSTANTS } from '../../constants.js';
|
|
10
|
+
/**
|
|
11
|
+
* Actions that need the owner's explicit OK even with the autopilot on.
|
|
12
|
+
* Spelled out in the triage brief and in the team-leader prompt.
|
|
13
|
+
*/
|
|
14
|
+
export const TICKET_AUTOPILOT_BOUNDARIES = [
|
|
15
|
+
'sending email or messages to outside people',
|
|
16
|
+
'publishing content publicly',
|
|
17
|
+
'deploying to production',
|
|
18
|
+
'spending money',
|
|
19
|
+
];
|
|
20
|
+
/**
|
|
21
|
+
* Human age of a timestamp ("3h", "2d").
|
|
22
|
+
*
|
|
23
|
+
* @param iso - ISO time
|
|
24
|
+
* @param now - Clock (epoch ms)
|
|
25
|
+
* @returns Short age, or "?" when unreadable
|
|
26
|
+
*/
|
|
27
|
+
export function formatAge(iso, now) {
|
|
28
|
+
const t = Date.parse(iso);
|
|
29
|
+
if (!Number.isFinite(t))
|
|
30
|
+
return '?';
|
|
31
|
+
const minutes = Math.max(0, Math.floor((now - t) / 60_000));
|
|
32
|
+
if (minutes < 60)
|
|
33
|
+
return `${minutes}m`;
|
|
34
|
+
const hours = Math.floor(minutes / 60);
|
|
35
|
+
if (hours < 48)
|
|
36
|
+
return `${hours}h`;
|
|
37
|
+
return `${Math.floor(hours / 24)}d`;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Who created a ticket, for the brief.
|
|
41
|
+
*
|
|
42
|
+
* @param source - Ticket `source`
|
|
43
|
+
* @returns `owner`, the agent session, the request, or `unknown`
|
|
44
|
+
*/
|
|
45
|
+
function creatorOf(source) {
|
|
46
|
+
if (!source)
|
|
47
|
+
return 'unknown';
|
|
48
|
+
if (source.startsWith('agent:'))
|
|
49
|
+
return source.slice('agent:'.length);
|
|
50
|
+
return source;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Shorten free text to one excerpt line.
|
|
54
|
+
*
|
|
55
|
+
* @param text - Text
|
|
56
|
+
* @param max - Max characters
|
|
57
|
+
* @returns Single-line excerpt
|
|
58
|
+
*/
|
|
59
|
+
function excerpt(text, max) {
|
|
60
|
+
const flat = text.replace(/\s+/g, ' ').trim();
|
|
61
|
+
return flat.length > max ? `${flat.slice(0, max - 1)}…` : flat;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* The triage brief the driver receives (WorkItem `briefMarkdown`).
|
|
65
|
+
*
|
|
66
|
+
* @param input - Project, tickets, team, limits, clock
|
|
67
|
+
* @returns Markdown brief
|
|
68
|
+
*/
|
|
69
|
+
export function buildTriageBrief(input) {
|
|
70
|
+
const p = input.project.id;
|
|
71
|
+
const tk = '$AGENT_SKILLS_PATH/core/project-tickets/execute.sh';
|
|
72
|
+
const lines = [
|
|
73
|
+
`# Ticket triage — ${input.project.name} (${input.candidates.length} ticket${input.candidates.length === 1 ? '' : 's'})`,
|
|
74
|
+
'',
|
|
75
|
+
'Ticket autopilot is on for this project: you keep its backlog moving while the owner is away (on a phone).',
|
|
76
|
+
'Decide every ticket below, then complete this WorkItem (complete-task with its id) with one line per ticket saying what you did.',
|
|
77
|
+
'',
|
|
78
|
+
'## For each ticket, do one of these',
|
|
79
|
+
'',
|
|
80
|
+
`1. **Ready + assign** — \`bash ${tk} assign --project ${p} --id <ID> --to <member>\`, or \`… update --project ${p} --id <ID> --status ready\` to let the next idle member take it. At most ${input.maxInFlightPerMember} ticket${input.maxInFlightPerMember === 1 ? '' : 's'} in progress per member.`,
|
|
81
|
+
`2. **Split** — create smaller tickets (\`… create --project ${p} --title "…" --acceptance "…" --status ready\`), then cancel the original with a note naming the new ids.`,
|
|
82
|
+
`3. **Needs the owner** — \`bash ${tk} ask-owner --project ${p} --id <ID> --question "<one line the owner can answer with a tap or a word>"\`. Crewly batches these to the owner's phone; do not message the owner about them yourself.`,
|
|
83
|
+
`4. **Cancel** — \`… update --project ${p} --id <ID> --status cancelled --note "<reason>"\`.`,
|
|
84
|
+
'',
|
|
85
|
+
'## Boundaries — the autopilot does NOT lift these',
|
|
86
|
+
'',
|
|
87
|
+
'Even with the autopilot on, these need the owner\'s explicit OK:',
|
|
88
|
+
...TICKET_AUTOPILOT_BOUNDARIES.map((b) => `- ${b};`),
|
|
89
|
+
'',
|
|
90
|
+
'A ticket whose completion needs one of these may be worked up to a draft or a PR; then use ask-owner for the final step.',
|
|
91
|
+
'"The autopilot is on" is never that OK, and neither is your own judgement.',
|
|
92
|
+
'',
|
|
93
|
+
'Tickets marked **worker-created — review first** were filed by a team member, not by the owner, a lead or the orchestrator.',
|
|
94
|
+
'Check that they are wanted and in scope before making them ready; if unsure, ask the owner.',
|
|
95
|
+
'',
|
|
96
|
+
'## Team',
|
|
97
|
+
'',
|
|
98
|
+
...(input.members.length > 0
|
|
99
|
+
? input.members.map((m) => `- ${m.session}${m.name && m.name !== m.session ? ` (${m.name}${m.role ? `, ${m.role}` : ''})` : m.role ? ` (${m.role})` : ''} — ${m.idle ? 'idle' : 'busy'}, ${m.inFlight} in progress`)
|
|
100
|
+
: ['- (no members found)']),
|
|
101
|
+
'',
|
|
102
|
+
'## Tickets',
|
|
103
|
+
'',
|
|
104
|
+
];
|
|
105
|
+
for (const c of input.candidates) {
|
|
106
|
+
const t = c.ticket;
|
|
107
|
+
const why = c.reason === 'backlog' ? 'backlog' : c.reason === 'ready_no_taker' ? 'ready, but nobody on the team can take it' : 'ready, but nobody has taken it for a day';
|
|
108
|
+
lines.push(`### ${t.id} · ${t.priority} · ${why} · ${formatAge(t.createdAt, input.now)} old`);
|
|
109
|
+
lines.push(t.title);
|
|
110
|
+
const meta = [`created by ${creatorOf(t.source)}`];
|
|
111
|
+
if (t.labels.length > 0)
|
|
112
|
+
meta.push(`labels: ${t.labels.join(', ')}`);
|
|
113
|
+
if (t.team)
|
|
114
|
+
meta.push(`team: ${t.team}`);
|
|
115
|
+
lines.push(`${meta.join(' · ')}${c.workerCreated ? ' · **worker-created — review first**' : ''}`);
|
|
116
|
+
if (t.description)
|
|
117
|
+
lines.push(`> ${excerpt(t.description, TICKET_AUTOPILOT_CONSTANTS.TRIAGE_DESCRIPTION_EXCERPT_CHARS)}`);
|
|
118
|
+
lines.push('');
|
|
119
|
+
}
|
|
120
|
+
if (input.more > 0)
|
|
121
|
+
lines.push(`${input.more} more ticket${input.more === 1 ? '' : 's'} will come in the next triage.`, '');
|
|
122
|
+
lines.push(`Full ticket: \`bash ${tk} show --project ${p} --id <ID>\`.`);
|
|
123
|
+
return lines.join('\n');
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* The one batched message with every open owner question — numbered, short,
|
|
127
|
+
* answerable with a tap or a word.
|
|
128
|
+
*
|
|
129
|
+
* @param items - Questions (already ordered)
|
|
130
|
+
* @returns Message text
|
|
131
|
+
*/
|
|
132
|
+
export function buildOwnerQuestionsMessage(items) {
|
|
133
|
+
const multiProject = new Set(items.map((i) => i.projectName)).size > 1;
|
|
134
|
+
const lines = [
|
|
135
|
+
items.length === 1 ? 'One ticket is waiting on you:' : `${items.length} tickets are waiting on you:`,
|
|
136
|
+
'',
|
|
137
|
+
...items.map((q, i) => `${i + 1}. ${q.ticketId}${multiProject ? ` (${q.projectName})` : ''} ${excerpt(q.title, 60)} — ${q.question}`),
|
|
138
|
+
'',
|
|
139
|
+
'Reply with the number and your answer (e.g. "1 yes").',
|
|
140
|
+
];
|
|
141
|
+
return lines.join('\n');
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* One digest section line: count plus the first few tickets.
|
|
145
|
+
*
|
|
146
|
+
* @param label - Section label
|
|
147
|
+
* @param tickets - Tickets
|
|
148
|
+
* @param withAssignee - Append the assignee
|
|
149
|
+
* @returns Line, or null when empty
|
|
150
|
+
*/
|
|
151
|
+
function digestSection(label, tickets, withAssignee) {
|
|
152
|
+
if (tickets.length === 0)
|
|
153
|
+
return null;
|
|
154
|
+
const max = TICKET_AUTOPILOT_CONSTANTS.DIGEST_MAX_ITEMS_PER_SECTION;
|
|
155
|
+
const named = tickets
|
|
156
|
+
.slice(0, max)
|
|
157
|
+
.map((t) => `${t.id} ${excerpt(t.title, 50)}${withAssignee && t.assignee ? ` (${t.assignee})` : ''}`);
|
|
158
|
+
const rest = tickets.length > max ? `; +${tickets.length - max} more` : '';
|
|
159
|
+
return `${label} (${tickets.length}): ${named.join('; ')}${rest}`;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* The evening digest: per project, done today / in progress / waiting on the
|
|
163
|
+
* owner. Projects with nothing to report are left out.
|
|
164
|
+
*
|
|
165
|
+
* @param projects - Per-project sections
|
|
166
|
+
* @returns Message text, or null when there is nothing to say
|
|
167
|
+
*/
|
|
168
|
+
export function buildDigestMessage(projects) {
|
|
169
|
+
const blocks = [];
|
|
170
|
+
for (const p of projects) {
|
|
171
|
+
const rows = [
|
|
172
|
+
digestSection('Done today', p.doneToday, false),
|
|
173
|
+
digestSection('In progress', p.inProgress, true),
|
|
174
|
+
digestSection('Waiting on you', p.waitingOnOwner, false),
|
|
175
|
+
].filter((r) => r !== null);
|
|
176
|
+
if (rows.length === 0)
|
|
177
|
+
continue;
|
|
178
|
+
blocks.push([`*${p.name}*`, ...rows.map((r) => `- ${r}`)].join('\n'));
|
|
179
|
+
}
|
|
180
|
+
if (blocks.length === 0)
|
|
181
|
+
return null;
|
|
182
|
+
return ['Tickets today', '', blocks.join('\n\n')].join('\n');
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* The once-a-day notice that the autopilot paused on its budget.
|
|
186
|
+
*
|
|
187
|
+
* @param projectName - Project
|
|
188
|
+
* @param spentUsd - Spent today
|
|
189
|
+
* @param budgetUsd - Daily budget
|
|
190
|
+
* @returns Message text
|
|
191
|
+
*/
|
|
192
|
+
export function buildBudgetPausedMessage(projectName, spentUsd, budgetUsd) {
|
|
193
|
+
return `Ticket autopilot paused for today on ${projectName}: the team has used $${spentUsd.toFixed(2)} of its $${budgetUsd.toFixed(2)} daily budget. It picks up again tomorrow. Reply "raise the budget to $N" to change it.`;
|
|
194
|
+
}
|
|
195
|
+
//# sourceMappingURL=ticket-autopilot-messages.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ticket-autopilot-messages.js","sourceRoot":"","sources":["../../../../../../backend/src/services/project-tickets/ticket-autopilot-messages.ts"],"names":[],"mappings":"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"}
|