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.
- 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 +84 -1
- package/dist/backend/backend/src/constants.d.ts.map +1 -1
- package/dist/backend/backend/src/constants.js +105 -16
- package/dist/backend/backend/src/constants.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/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 +84 -1
- package/dist/cli/backend/src/constants.d.ts.map +1 -1
- package/dist/cli/backend/src/constants.js +105 -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-8f30aa74.js → index-64c8a616.js} +2 -2
- package/frontend/dist/index.html +1 -1
- 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":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,yBAAyB,EAAE,wBAAwB,EAAE,0BAA0B,EAAE,MAAM,oBAAoB,CAAC;AACrH,OAAO,EAAE,yBAAyB,EAAsB,MAAM,qCAAqC,CAAC;AAEpG,OAAO,EAAE,UAAU,EAAE,MAAM,sCAAsC,CAAC;AA8ElE;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB,CAAC,MAAqC;IACtE,OAAO,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,0BAA0B,CAAC,iBAAiB,CAAC,CAAC;AAC9E,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,iBAAiB,CAAC,MAAkC;IAClE,MAAM,MAAM,GAAG,0BAA0B,CAAC,yBAAyB,CAAC;IACpE,KAAK,IAAI,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QAChD,MAAM,EAAE,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QACzC,IAAI,EAAE,IAAI,CAAC,EAAE,CAAC;YACZ,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;YAC5D,IAAI,IAAI;gBAAE,OAAO,IAAI,CAAC;QACxB,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,eAAe,CAAC,MAAqC,EAAE,KAAa;IAClF,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,IAAI,EAAE,CAAC;IACnC,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC;QAAE,OAAO,KAAK,CAAC;IAC/C,MAAM,OAAO,GAAG,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;IACrD,IAAI,CAAC,OAAO,IAAI,OAAO,KAAK,yBAAyB;QAAE,OAAO,KAAK,CAAC;IACpE,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,OAAO,IAAI,EAAE,EAAE,CAAC;YACnC,IAAI,CAAC,CAAC,CAAC,WAAW,KAAK,OAAO,IAAI,CAAC,CAAC,OAAO,KAAK,OAAO,CAAC,IAAI,UAAU,CAAC,IAAI,EAAE,CAAC,CAAC;gBAAE,OAAO,KAAK,CAAC;QAChG,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,YAAY,CAAC,MAAyD;IACpF,OAAO,CAAC,MAAM,CAAC,WAAW,KAAK,QAAQ,IAAI,MAAM,CAAC,WAAW,KAAK,SAAS,CAAC,IAAI,MAAM,CAAC,aAAa,KAAK,MAAM,CAAC;AAClH,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,gBAAgB,CAAC,MAAmC,EAAE,KAAa;IACjF,OAAO,KAAK;SACT,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,IAAI,IAAI,CAAC,CAAC,EAAE,KAAK,MAAM,CAAC,IAAI,CAAC;SACnD,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE;QACV,MAAM,OAAO,GAAG,CAAC,CAAC,OAAO,IAAI,EAAE,CAAC;QAChC,OAAO,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,UAAU,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC;IACxE,CAAC,CAAC,CAAC;AACP,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,sBAAsB,CAAC,KAAwB;IAC7D,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,IAAI,EAAE,CAAC;IAClC,MAAM,GAAG,GAAsB,EAAE,CAAC;IAClC,KAAK,MAAM,MAAM,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;QACnC,IAAI,MAAM,GAAwB,IAAI,CAAC;QACvC,IAAI,MAAM,CAAC,MAAM,KAAK,SAAS,IAAI,CAAC,kBAAkB,CAAC,MAAM,CAAC,EAAE,CAAC;YAC/D,MAAM,GAAG,SAAS,CAAC;QACrB,CAAC;aAAM,IAAI,MAAM,CAAC,MAAM,KAAK,OAAO,IAAI,CAAC,kBAAkB,CAAC,MAAM,CAAC,EAAE,CAAC;YACpE,IAAI,CAAC,gBAAgB,CAAC,MAAM,EAAE,KAAK,CAAC,KAAK,CAAC;gBAAE,MAAM,GAAG,gBAAgB,CAAC;iBACjE,IAAI,KAAK,CAAC,GAAG,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,KAAK,CAAC,GAAG,CAAC,IAAI,0BAA0B,CAAC,cAAc;gBAAE,MAAM,GAAG,aAAa,CAAC;QACxI,CAAC;QACD,IAAI,CAAC,MAAM;YAAE,SAAS;QACtB,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QAC/B,IAAI,IAAI,IAAI,IAAI,CAAC,SAAS,KAAK,MAAM,CAAC,SAAS,IAAI,KAAK,CAAC,GAAG,GAAG,IAAI,CAAC,EAAE,GAAG,0BAA0B,CAAC,sBAAsB;YAAE,SAAS;QACrI,GAAG,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,EAAE,eAAe,CAAC,MAAM,EAAE,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IACpF,CAAC;IACD,GAAG,CAAC,IAAI,CACN,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CACP,yBAAyB,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,yBAAyB,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC;QAC3F,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;QAC7E,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,aAAa,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,CACzC,CAAC;IACF,MAAM,GAAG,GAAG,0BAA0B,CAAC,kBAAkB,CAAC;IAC1D,OAAO,EAAE,UAAU,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,CAAC,MAAM,GAAG,GAAG,CAAC,EAAE,CAAC;AAChF,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,YAAY,CAAC,KAA0B;IACrD,IAAI,CAAC,KAAK,CAAC,OAAO;QAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;IAC7D,IAAI,CAAC,KAAK,CAAC,MAAM;QAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC;IAClE,IAAI,KAAK,CAAC,aAAa,IAAI,KAAK,CAAC,cAAc;QAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,gBAAgB,EAAE,CAAC;IACrG,IAAI,KAAK,CAAC,UAAU;QAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,kBAAkB,EAAE,CAAC;IAC5E,IAAI,KAAK,CAAC,cAAc,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,mBAAmB,EAAE,CAAC;IACvF,IAAI,CAAC,KAAK,CAAC,UAAU;QAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,EAAE,CAAC;IACxE,MAAM,GAAG,GACP,KAAK,CAAC,OAAO,KAAK,aAAa,CAAC,CAAC,CAAC,0BAA0B,CAAC,4BAA4B,CAAC,CAAC,CAAC,0BAA0B,CAAC,sBAAsB,CAAC;IAChJ,IAAI,KAAK,CAAC,YAAY,KAAK,SAAS,IAAI,KAAK,CAAC,GAAG,GAAG,KAAK,CAAC,YAAY,GAAG,GAAG;QAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC;IAC5H,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;AAC9B,CAAC;AAmBD;;;;;;;;GAQG;AACH,MAAM,UAAU,oBAAoB,CAAC,KAA0B;IAC7D,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IACtE,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,aAAa,EAAE,CAAC;IACtE,IAAI,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,0BAA0B,CAAC,eAAe,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;IAC1H,IAAI,KAAK,CAAC,UAAU,KAAK,SAAS,IAAI,KAAK,CAAC,GAAG,GAAG,KAAK,CAAC,UAAU,IAAI,0BAA0B,CAAC,yBAAyB,EAAE,CAAC;QAC3H,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;IACvC,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC;AAC7C,CAAC;AAaD;;;;;;;;GAQG;AACH,MAAM,UAAU,YAAY,CAAC,KAA0B;IACrD,IAAI,KAAK,CAAC,GAAG,CAAC,QAAQ,EAAE,GAAG,0BAA0B,CAAC,iBAAiB;QAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;IACnH,IAAI,KAAK,CAAC,YAAY,KAAK,YAAY,CAAC,KAAK,CAAC,GAAG,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,cAAc,EAAE,CAAC;IACnG,MAAM,KAAK,GAAG,KAAK,CAAC,UAAU,IAAI,KAAK,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;IAC5E,IAAI,CAAC,CAAC,KAAK,CAAC,oBAAoB,GAAG,KAAK,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,iBAAiB,EAAE,CAAC;IAC7F,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;AACvC,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,aAAa,CAAC,GAAS;IACrC,MAAM,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;IAClC,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;IACvB,OAAO,CAAC,CAAC;AACX,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,YAAY,CAAC,GAAS;IACpC,MAAM,GAAG,GAAG,CAAC,CAAS,EAAU,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IAC9D,OAAO,GAAG,GAAG,CAAC,WAAW,EAAE,IAAI,GAAG,CAAC,GAAG,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,IAAI,GAAG,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC;AACjF,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB,CAAC,OAAwB;IACzD,MAAM,GAAG,GAAG,IAAI,GAAG,EAAkB,CAAC;IACtC,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;QACxB,IAAI,CAAC,CAAC,MAAM,KAAK,aAAa,IAAI,CAAC,CAAC,QAAQ;YAAE,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IACpG,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,wFAAwF;AACxF,MAAM,CAAC,MAAM,oBAAoB,GAAwB,IAAI,GAAG,CAC9D,wBAAwB,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,MAAM,IAAI,CAAC,KAAK,WAAW,CAAC,CACnF,CAAC"}
|
|
@@ -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"}
|