crewly 1.20.204 → 1.20.206
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/skills/_common/lib.sh +4 -3
- package/config/skills/agent/core/docs-comment/SKILL.md +121 -0
- package/config/skills/agent/core/docs-comment/execute.sh +131 -0
- package/config/skills/agent/core/docs-comment/execute.test.sh +105 -0
- package/config/skills/agent/core/project-tickets/SKILL.md +13 -1
- package/config/skills/agent/core/project-tickets/execute.sh +20 -6
- package/config/skills/agent/core/project-tickets/execute.test.sh +8 -2
- package/dist/backend/backend/src/constants.d.ts +76 -0
- package/dist/backend/backend/src/constants.d.ts.map +1 -1
- package/dist/backend/backend/src/constants.js +82 -0
- package/dist/backend/backend/src/constants.js.map +1 -1
- package/dist/backend/backend/src/controllers/google/google.controller.d.ts +30 -0
- package/dist/backend/backend/src/controllers/google/google.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/google/google.controller.js +75 -0
- package/dist/backend/backend/src/controllers/google/google.controller.js.map +1 -1
- package/dist/backend/backend/src/controllers/google/google.routes.d.ts +4 -0
- package/dist/backend/backend/src/controllers/google/google.routes.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/google/google.routes.js +9 -1
- package/dist/backend/backend/src/controllers/google/google.routes.js.map +1 -1
- package/dist/backend/backend/src/controllers/project-tickets/project-tickets.controller.d.ts +1 -1
- 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 +9 -1
- 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 +1 -1
- package/dist/backend/backend/src/controllers/project-tickets/project-tickets.routes.js +1 -1
- package/dist/backend/backend/src/controllers/settings/role.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/settings/role.controller.js +21 -7
- package/dist/backend/backend/src/controllers/settings/role.controller.js.map +1 -1
- package/dist/backend/backend/src/controllers/settings/settings.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/settings/settings.controller.js +26 -20
- package/dist/backend/backend/src/controllers/settings/settings.controller.js.map +1 -1
- package/dist/backend/backend/src/controllers/skill/skill.controller.d.ts +13 -0
- package/dist/backend/backend/src/controllers/skill/skill.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/skill/skill.controller.js +21 -0
- package/dist/backend/backend/src/controllers/skill/skill.controller.js.map +1 -1
- package/dist/backend/backend/src/controllers/system/system-control.controller.d.ts +11 -1
- package/dist/backend/backend/src/controllers/system/system-control.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/system/system-control.controller.js +36 -2
- package/dist/backend/backend/src/controllers/system/system-control.controller.js.map +1 -1
- package/dist/backend/backend/src/index.d.ts +9 -0
- package/dist/backend/backend/src/index.d.ts.map +1 -1
- package/dist/backend/backend/src/index.js +28 -0
- package/dist/backend/backend/src/index.js.map +1 -1
- package/dist/backend/backend/src/middleware/agent-origin.middleware.d.ts +3 -2
- package/dist/backend/backend/src/middleware/agent-origin.middleware.d.ts.map +1 -1
- package/dist/backend/backend/src/middleware/agent-origin.middleware.js +5 -3
- package/dist/backend/backend/src/middleware/agent-origin.middleware.js.map +1 -1
- package/dist/backend/backend/src/middleware/caller-identity.middleware.d.ts +36 -0
- package/dist/backend/backend/src/middleware/caller-identity.middleware.d.ts.map +1 -1
- package/dist/backend/backend/src/middleware/caller-identity.middleware.js +97 -1
- package/dist/backend/backend/src/middleware/caller-identity.middleware.js.map +1 -1
- package/dist/backend/backend/src/routes/modules/terminal.routes.d.ts +4 -0
- package/dist/backend/backend/src/routes/modules/terminal.routes.d.ts.map +1 -1
- package/dist/backend/backend/src/routes/modules/terminal.routes.js +19 -5
- package/dist/backend/backend/src/routes/modules/terminal.routes.js.map +1 -1
- package/dist/backend/backend/src/scripts/input-guard-classify.d.ts +44 -0
- package/dist/backend/backend/src/scripts/input-guard-classify.d.ts.map +1 -0
- package/dist/backend/backend/src/scripts/input-guard-classify.js +116 -0
- package/dist/backend/backend/src/scripts/input-guard-classify.js.map +1 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/crewly-agent-external-runtime.service.d.ts +8 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/crewly-agent-external-runtime.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/agent/crewly-agent/crewly-agent-external-runtime.service.js +10 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/crewly-agent-external-runtime.service.js.map +1 -1
- package/dist/backend/backend/src/services/agent/crewly-agent/in-process-runtime-registry.d.ts +8 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/in-process-runtime-registry.d.ts.map +1 -1
- package/dist/backend/backend/src/services/agent/crewly-agent/in-process-runtime-registry.js +21 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/in-process-runtime-registry.js.map +1 -1
- package/dist/backend/backend/src/services/agent/runtime-exit-monitor.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/agent/runtime-exit-monitor.service.js +13 -27
- package/dist/backend/backend/src/services/agent/runtime-exit-monitor.service.js.map +1 -1
- package/dist/backend/backend/src/services/ai/prompt-modules/skills-reference.module.d.ts.map +1 -1
- package/dist/backend/backend/src/services/ai/prompt-modules/skills-reference.module.js +1 -0
- package/dist/backend/backend/src/services/ai/prompt-modules/skills-reference.module.js.map +1 -1
- package/dist/backend/backend/src/services/google/docs-comments.service.d.ts +171 -0
- package/dist/backend/backend/src/services/google/docs-comments.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/google/docs-comments.service.js +218 -0
- package/dist/backend/backend/src/services/google/docs-comments.service.js.map +1 -0
- package/dist/backend/backend/src/services/google/google-api.client.d.ts +8 -0
- package/dist/backend/backend/src/services/google/google-api.client.d.ts.map +1 -1
- package/dist/backend/backend/src/services/google/google-api.client.js.map +1 -1
- package/dist/backend/backend/src/services/google/google-workspace-token.service.d.ts +10 -0
- package/dist/backend/backend/src/services/google/google-workspace-token.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/google/google-workspace-token.service.js +10 -0
- package/dist/backend/backend/src/services/google/google-workspace-token.service.js.map +1 -1
- package/dist/backend/backend/src/services/messaging/forced-delivery.d.ts +75 -0
- package/dist/backend/backend/src/services/messaging/forced-delivery.d.ts.map +1 -0
- package/dist/backend/backend/src/services/messaging/forced-delivery.js +87 -0
- package/dist/backend/backend/src/services/messaging/forced-delivery.js.map +1 -0
- package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-decision.d.ts +130 -4
- package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-decision.d.ts.map +1 -1
- package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-decision.js +115 -3
- package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-decision.js.map +1 -1
- package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-goal.d.ts +122 -0
- package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-goal.d.ts.map +1 -0
- package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-goal.js +274 -0
- package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-goal.js.map +1 -0
- package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-messages.d.ts +34 -2
- package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-messages.d.ts.map +1 -1
- package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-messages.js +59 -2
- package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-messages.js.map +1 -1
- package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-retro.js +1 -1
- package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-retro.js.map +1 -1
- package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-stats.d.ts +6 -1
- package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-stats.d.ts.map +1 -1
- package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-stats.js +9 -1
- package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-stats.js.map +1 -1
- package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-trace.d.ts +13 -3
- package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-trace.d.ts.map +1 -1
- package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-trace.js +28 -2
- package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-trace.js.map +1 -1
- package/dist/backend/backend/src/services/project-tickets/ticket-autopilot.service.d.ts +92 -7
- package/dist/backend/backend/src/services/project-tickets/ticket-autopilot.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/project-tickets/ticket-autopilot.service.js +316 -41
- package/dist/backend/backend/src/services/project-tickets/ticket-autopilot.service.js.map +1 -1
- package/dist/backend/backend/src/services/settings/api-key-access.service.d.ts +126 -0
- package/dist/backend/backend/src/services/settings/api-key-access.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/settings/api-key-access.service.js +223 -0
- package/dist/backend/backend/src/services/settings/api-key-access.service.js.map +1 -0
- package/dist/backend/backend/src/services/skill/skill-executor.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/skill/skill-executor.service.js +17 -0
- package/dist/backend/backend/src/services/skill/skill-executor.service.js.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-orchestrator-bridge.d.ts.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-orchestrator-bridge.js +29 -8
- package/dist/backend/backend/src/services/slack/slack-orchestrator-bridge.js.map +1 -1
- package/dist/backend/backend/src/services/system/auto-update.service.d.ts +24 -0
- package/dist/backend/backend/src/services/system/auto-update.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/system/auto-update.service.js +78 -8
- package/dist/backend/backend/src/services/system/auto-update.service.js.map +1 -1
- package/dist/backend/backend/src/services/system/auto-update.utils.d.ts +1 -1
- package/dist/backend/backend/src/services/system/auto-update.utils.d.ts.map +1 -1
- package/dist/backend/backend/src/services/system/auto-update.utils.js.map +1 -1
- package/dist/backend/backend/src/services/system/input-guard-release-check.d.ts +162 -0
- package/dist/backend/backend/src/services/system/input-guard-release-check.d.ts.map +1 -0
- package/dist/backend/backend/src/services/system/input-guard-release-check.js +264 -0
- package/dist/backend/backend/src/services/system/input-guard-release-check.js.map +1 -0
- package/dist/backend/backend/src/services/system/system-control.service.d.ts +36 -0
- package/dist/backend/backend/src/services/system/system-control.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/system/system-control.service.js +109 -10
- package/dist/backend/backend/src/services/system/system-control.service.js.map +1 -1
- package/dist/backend/backend/src/types/skill.types.d.ts +16 -0
- package/dist/backend/backend/src/types/skill.types.d.ts.map +1 -1
- package/dist/backend/backend/src/types/skill.types.js.map +1 -1
- package/dist/backend/backend/src/types/ticket-autopilot.types.d.ts +21 -0
- package/dist/backend/backend/src/types/ticket-autopilot.types.d.ts.map +1 -1
- package/dist/backend/backend/src/types/ticket-autopilot.types.js +48 -0
- package/dist/backend/backend/src/types/ticket-autopilot.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 +76 -0
- package/dist/cli/backend/src/constants.d.ts.map +1 -1
- package/dist/cli/backend/src/constants.js +82 -0
- package/dist/cli/backend/src/constants.js.map +1 -1
- package/dist/cli/backend/src/scripts/input-guard-classify.d.ts +44 -0
- package/dist/cli/backend/src/scripts/input-guard-classify.d.ts.map +1 -0
- package/dist/cli/backend/src/scripts/input-guard-classify.js +116 -0
- package/dist/cli/backend/src/scripts/input-guard-classify.js.map +1 -0
- package/dist/cli/backend/src/services/agent/crewly-agent/crewly-agent-external-runtime.service.d.ts +8 -0
- package/dist/cli/backend/src/services/agent/crewly-agent/crewly-agent-external-runtime.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/agent/crewly-agent/crewly-agent-external-runtime.service.js +10 -0
- package/dist/cli/backend/src/services/agent/crewly-agent/crewly-agent-external-runtime.service.js.map +1 -1
- package/dist/cli/backend/src/services/agent/crewly-agent/in-process-runtime-registry.d.ts +8 -0
- package/dist/cli/backend/src/services/agent/crewly-agent/in-process-runtime-registry.d.ts.map +1 -1
- package/dist/cli/backend/src/services/agent/crewly-agent/in-process-runtime-registry.js +21 -0
- package/dist/cli/backend/src/services/agent/crewly-agent/in-process-runtime-registry.js.map +1 -1
- package/dist/cli/backend/src/services/agent/runtime-exit-monitor.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/agent/runtime-exit-monitor.service.js +13 -27
- package/dist/cli/backend/src/services/agent/runtime-exit-monitor.service.js.map +1 -1
- package/dist/cli/backend/src/services/ai/prompt-modules/skills-reference.module.d.ts.map +1 -1
- package/dist/cli/backend/src/services/ai/prompt-modules/skills-reference.module.js +1 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/skills-reference.module.js.map +1 -1
- package/dist/cli/backend/src/services/messaging/drain-queued-delivery.d.ts +59 -0
- package/dist/cli/backend/src/services/messaging/drain-queued-delivery.d.ts.map +1 -0
- package/dist/cli/backend/src/services/messaging/drain-queued-delivery.js +70 -0
- package/dist/cli/backend/src/services/messaging/drain-queued-delivery.js.map +1 -0
- package/dist/cli/backend/src/services/messaging/forced-delivery.d.ts +75 -0
- package/dist/cli/backend/src/services/messaging/forced-delivery.d.ts.map +1 -0
- package/dist/cli/backend/src/services/messaging/forced-delivery.js +87 -0
- package/dist/cli/backend/src/services/messaging/forced-delivery.js.map +1 -0
- package/dist/cli/backend/src/services/messaging/spend-capped-delivery.d.ts +54 -0
- package/dist/cli/backend/src/services/messaging/spend-capped-delivery.d.ts.map +1 -0
- package/dist/cli/backend/src/services/messaging/spend-capped-delivery.js +65 -0
- package/dist/cli/backend/src/services/messaging/spend-capped-delivery.js.map +1 -0
- package/dist/cli/backend/src/services/skill/skill-executor.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/skill/skill-executor.service.js +17 -0
- package/dist/cli/backend/src/services/skill/skill-executor.service.js.map +1 -1
- package/dist/cli/backend/src/services/slack/slack-orchestrator-bridge.d.ts.map +1 -1
- package/dist/cli/backend/src/services/slack/slack-orchestrator-bridge.js +29 -8
- package/dist/cli/backend/src/services/slack/slack-orchestrator-bridge.js.map +1 -1
- package/dist/cli/backend/src/services/system/auto-update.utils.d.ts +1 -1
- package/dist/cli/backend/src/services/system/auto-update.utils.d.ts.map +1 -1
- package/dist/cli/backend/src/services/system/auto-update.utils.js.map +1 -1
- package/dist/cli/backend/src/services/system/input-guard-release-check.d.ts +162 -0
- package/dist/cli/backend/src/services/system/input-guard-release-check.d.ts.map +1 -0
- package/dist/cli/backend/src/services/system/input-guard-release-check.js +264 -0
- package/dist/cli/backend/src/services/system/input-guard-release-check.js.map +1 -0
- package/dist/cli/backend/src/types/skill.types.d.ts +16 -0
- package/dist/cli/backend/src/types/skill.types.d.ts.map +1 -1
- package/dist/cli/backend/src/types/skill.types.js.map +1 -1
- package/dist/cli/backend/src/types/ticket-autopilot.types.d.ts +21 -0
- package/dist/cli/backend/src/types/ticket-autopilot.types.d.ts.map +1 -1
- package/dist/cli/backend/src/types/ticket-autopilot.types.js +48 -0
- package/dist/cli/backend/src/types/ticket-autopilot.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/dist/cli/cli/src/commands/doctor.d.ts +20 -0
- package/dist/cli/cli/src/commands/doctor.d.ts.map +1 -1
- package/dist/cli/cli/src/commands/doctor.js +49 -0
- package/dist/cli/cli/src/commands/doctor.js.map +1 -1
- package/dist/cli/cli/src/index.js +9 -2
- package/dist/cli/cli/src/index.js.map +1 -1
- package/frontend/dist/assets/{index-63a33dbf.js → index-f64b333a.js} +258 -258
- package/frontend/dist/index.html +1 -1
- package/package.json +2 -1
|
@@ -10,6 +10,10 @@
|
|
|
10
10
|
* - brakes hold: a daily USD budget over the team's agents (pauses the
|
|
11
11
|
* autopilot and ticket auto-claim for the day, owner told once) and an
|
|
12
12
|
* in-progress cap per member ({@link ProjectTicketAutopilotPolicy});
|
|
13
|
+
* - when the project has a goal but nothing is left to triage and someone is
|
|
14
|
+
* idle, the driver is woken with ONE `goal_replan` WorkItem to open the
|
|
15
|
+
* next tickets toward the goal (at most `replansPerDay` a day, same budget
|
|
16
|
+
* and in-progress brakes; specs/2026-10-04-autopilot-goal-replan.md);
|
|
13
17
|
* - owner questions are decision cards (specs/2026-10-01-decision-cards.md),
|
|
14
18
|
* posted by the asking agent in the ticket's thread; the owner gets one
|
|
15
19
|
* evening digest (skipped when nothing changed) that links to open cards.
|
|
@@ -28,9 +32,9 @@ import { applyTicketAutopilotInput, legacyBudgetTokens, resolveTicketAutopilotSe
|
|
|
28
32
|
import { ProjectTicketError } from './project-ticket.service.js';
|
|
29
33
|
import { getTeamLeads } from '../../utils/team.utils.js';
|
|
30
34
|
import { isTeamLead, } from './project-ticket-workflow.service.js';
|
|
31
|
-
import { OPEN_TICKET_STATUSES, decideDigest, decideTriage, hasNeedsOwnerLabel, inFlightByAssignee, isMemberIdle, localDateKey, memberAvailability, memberResponsibility, localMidnight, selectTriageCandidates, } from './ticket-autopilot-decision.js';
|
|
32
|
-
import { buildBudgetPausedMessage, buildDigestMessage, buildTriageBrief, } from './ticket-autopilot-messages.js';
|
|
33
|
-
import { autopilotRunTrace, startTriageTrace, traceAutopilotAction, createAutopilotTicketListener } from './ticket-autopilot-trace.js';
|
|
35
|
+
import { OPEN_TICKET_STATUSES, closedTicketsSince, decideDigest, decideReplan, decideTriage, nextReplanBackoff, replanBackoffState, hasNeedsOwnerLabel, inFlightByAssignee, isMemberIdle, localDateKey, memberAvailability, memberResponsibility, localMidnight, selectTriageCandidates, } from './ticket-autopilot-decision.js';
|
|
36
|
+
import { buildBudgetPausedMessage, buildDigestMessage, buildReplanBrief, buildTriageBrief, REPLAN_ASK, } from './ticket-autopilot-messages.js';
|
|
37
|
+
import { autopilotRunTrace, startReplanTrace, startTriageTrace, traceAutopilotAction, createAutopilotTicketListener } from './ticket-autopilot-trace.js';
|
|
34
38
|
import { computeAutopilotStats, dayEndMs, daysBetween, dayStartMs, rangeDays, } from './ticket-autopilot-stats.js';
|
|
35
39
|
import { buildRetroBrief, duplicateOf, renderRetroMarkdown, RetroInputError, validateRetroInput, } from './ticket-autopilot-retro.js';
|
|
36
40
|
import { getTraceStore } from '../trace/trace-store.js';
|
|
@@ -40,6 +44,7 @@ const SKIP_WORDS = {
|
|
|
40
44
|
no_driver: 'no team lead to drive it',
|
|
41
45
|
budget_reached: 'daily budget reached',
|
|
42
46
|
triage_in_flight: 'a triage is still open',
|
|
47
|
+
replan_in_flight: 'a goal replan is still open',
|
|
43
48
|
nothing_to_triage: 'nothing to triage',
|
|
44
49
|
nobody_idle: 'nobody on the team is idle',
|
|
45
50
|
too_soon: 'too soon after the last triage',
|
|
@@ -283,7 +288,8 @@ export class TicketAutopilotService {
|
|
|
283
288
|
await this.noticeBudgetPaused(project, ps, spent, budget, now, teams[0]?.name);
|
|
284
289
|
this.traceBudgetState(project, ps, settings.enabled && spent >= budget, spent, budget, now);
|
|
285
290
|
const { tickets } = await this.deps.tickets.list(project.path);
|
|
286
|
-
const live = await this.
|
|
291
|
+
const live = await this.liveItem(project, nowMs, TICKET_AUTOPILOT_CONSTANTS.TRIAGE_METADATA_KIND);
|
|
292
|
+
const liveReplan = await this.liveItem(project, nowMs, TICKET_AUTOPILOT_CONSTANTS.REPLAN_METADATA_KIND, settings.replanTtlHours);
|
|
287
293
|
const selection = selectTriageCandidates({ tickets, teams, now: nowMs, listed: ps.listed });
|
|
288
294
|
const members = teams.flatMap((t) => t.members ?? []);
|
|
289
295
|
const anyoneIdle = (!!idleSession && members.some((m) => sessionOf(m) === idleSession || m.sessionName === idleSession)) || members.some((m) => isMemberIdle(m));
|
|
@@ -294,45 +300,46 @@ export class TicketAutopilotService {
|
|
|
294
300
|
now: nowMs,
|
|
295
301
|
candidateCount: selection.candidates.length,
|
|
296
302
|
liveTriage: !!live,
|
|
303
|
+
...(liveReplan ? { liveReplanAt: Date.parse(liveReplan.createdAt) || nowMs } : {}),
|
|
297
304
|
lastTriageAt: ps.lastTriageAt,
|
|
298
305
|
anyoneIdle,
|
|
299
306
|
usedTodayTokens: spent,
|
|
300
307
|
dailyBudgetTokens: budget,
|
|
301
308
|
});
|
|
302
309
|
if (decision.action === 'skip' || !driver) {
|
|
310
|
+
// Nothing to triage: a project with a goal may wake its driver to plan
|
|
311
|
+
// the next tickets instead (specs/2026-10-04-autopilot-goal-replan.md).
|
|
312
|
+
if (decision.action === 'skip' && decision.reason === 'nothing_to_triage' && driver) {
|
|
313
|
+
const replan = await this.evaluateReplan(project, {
|
|
314
|
+
settings,
|
|
315
|
+
ps,
|
|
316
|
+
driver,
|
|
317
|
+
trigger,
|
|
318
|
+
now,
|
|
319
|
+
tickets,
|
|
320
|
+
teams,
|
|
321
|
+
idleSession,
|
|
322
|
+
spent,
|
|
323
|
+
budget,
|
|
324
|
+
liveTriage: !!live,
|
|
325
|
+
liveReplanId: liveReplan?.id ?? null,
|
|
326
|
+
candidateCount: selection.candidates.length,
|
|
327
|
+
anyoneIdle,
|
|
328
|
+
});
|
|
329
|
+
if (replan?.workItem)
|
|
330
|
+
return { projectId: project.id, decision, replan: replan.decision, workItem: replan.workItem };
|
|
331
|
+
this.logger.debug('Ticket autopilot: no triage', { projectId: project.id, trigger, decision, replan: replan?.decision });
|
|
332
|
+
this.traceSkip(project, ps, decision.reason, trigger, selection.candidates.length, now);
|
|
333
|
+
await this.saveState();
|
|
334
|
+
return { projectId: project.id, decision, ...(replan ? { replan: replan.decision } : {}) };
|
|
335
|
+
}
|
|
303
336
|
this.logger.debug('Ticket autopilot: no triage', { projectId: project.id, trigger, decision });
|
|
304
337
|
if (decision.action === 'skip')
|
|
305
338
|
this.traceSkip(project, ps, decision.reason, trigger, selection.candidates.length, now);
|
|
306
339
|
await this.saveState();
|
|
307
340
|
return { projectId: project.id, decision };
|
|
308
341
|
}
|
|
309
|
-
const
|
|
310
|
-
const seen = new Set();
|
|
311
|
-
const roster = teams.flatMap((t) => (t.members ?? [])
|
|
312
|
-
.filter((m) => {
|
|
313
|
-
const s = sessionOf(m);
|
|
314
|
-
if (!s || seen.has(s))
|
|
315
|
-
return false;
|
|
316
|
-
seen.add(s);
|
|
317
|
-
return true;
|
|
318
|
-
})
|
|
319
|
-
.map((m) => ({ team: t, m })));
|
|
320
|
-
const roleLines = await this.roleDescriptions(roster.map(({ m }) => m.role));
|
|
321
|
-
const briefMembers = roster.map(({ team: t, m }) => {
|
|
322
|
-
const session = sessionOf(m);
|
|
323
|
-
// Stopped (idle-stopped, suspended) is "available — started when
|
|
324
|
-
// assigned", never "busy"; its in-progress count is its real one.
|
|
325
|
-
const availability = session === idleSession && memberAvailability(m) !== 'stopped' ? 'idle' : memberAvailability(m);
|
|
326
|
-
return {
|
|
327
|
-
session,
|
|
328
|
-
name: m.name,
|
|
329
|
-
role: m.role,
|
|
330
|
-
lead: isTeamLead(t, m),
|
|
331
|
-
availability,
|
|
332
|
-
responsibility: memberResponsibility(m, roleLines.get(String(m.role ?? ''))),
|
|
333
|
-
inFlight: inFlight.get(session) ?? 0,
|
|
334
|
-
};
|
|
335
|
-
});
|
|
342
|
+
const briefMembers = await this.briefMembers(teams, tickets, idleSession);
|
|
336
343
|
const brief = buildTriageBrief({
|
|
337
344
|
project: { id: project.id, name: project.name },
|
|
338
345
|
candidates: selection.candidates,
|
|
@@ -390,22 +397,278 @@ export class TicketAutopilotService {
|
|
|
390
397
|
return { projectId: project.id, decision, workItem };
|
|
391
398
|
}
|
|
392
399
|
/**
|
|
393
|
-
*
|
|
394
|
-
*
|
|
395
|
-
*
|
|
400
|
+
* Evaluate a goal replan for a project with nothing to triage and, when
|
|
401
|
+
* {@link decideReplan} says so, wake the driver with ONE `goal_replan`
|
|
402
|
+
* WorkItem: the goal, the tickets closed lately, open experiment cards and
|
|
403
|
+
* the ask. The driver opens the tickets; the autopilot never makes them
|
|
404
|
+
* ready or starts work.
|
|
405
|
+
*
|
|
406
|
+
* @param project - Project
|
|
407
|
+
* @param c - The evaluation's state
|
|
408
|
+
* @returns The decision and the WorkItem created, or null when the project has no goal (nothing changes)
|
|
409
|
+
*/
|
|
410
|
+
async evaluateReplan(project, c) {
|
|
411
|
+
const C = TICKET_AUTOPILOT_CONSTANTS;
|
|
412
|
+
// No goal reader: never replans; the evaluation is as before.
|
|
413
|
+
if (!this.deps.goalOf)
|
|
414
|
+
return null;
|
|
415
|
+
const nowMs = c.now.getTime();
|
|
416
|
+
const today = localDateKey(c.now);
|
|
417
|
+
const inFlight = inFlightByAssignee(c.tickets);
|
|
418
|
+
const members = c.teams.flatMap((t) => t.members ?? []);
|
|
419
|
+
const isIdle = (m) => isMemberIdle(m) || (!!c.idleSession && (sessionOf(m) === c.idleSession || m.sessionName === c.idleSession));
|
|
420
|
+
const held = (m) => Math.max(inFlight.get(sessionOf(m)) ?? 0, m.sessionName ? inFlight.get(m.sessionName) ?? 0 : 0, m.agentId ? inFlight.get(m.agentId) ?? 0 : 0);
|
|
421
|
+
const idleWithRoom = members.some((m) => isIdle(m) && held(m) < c.settings.maxInFlightPerMember);
|
|
422
|
+
const replansToday = c.ps.replans?.day === today ? c.ps.replans.count : 0;
|
|
423
|
+
// The last replan is over: did it open tickets? None → back off longer.
|
|
424
|
+
this.assessLastReplan(project, c.ps, c.tickets, c.liveReplanId, nowMs);
|
|
425
|
+
// Cheap gates first (in-memory state only); the goal is read last.
|
|
426
|
+
const gates = {
|
|
427
|
+
enabled: c.settings.enabled,
|
|
428
|
+
driver: c.driver.session,
|
|
429
|
+
maxReplansPerDay: c.settings.replansPerDay,
|
|
430
|
+
replansToday,
|
|
431
|
+
usedTodayTokens: c.spent,
|
|
432
|
+
dailyBudgetTokens: c.budget,
|
|
433
|
+
liveTriage: c.liveTriage,
|
|
434
|
+
liveReplan: !!c.liveReplanId,
|
|
435
|
+
candidateCount: c.candidateCount,
|
|
436
|
+
anyoneIdle: c.anyoneIdle,
|
|
437
|
+
idleWithRoom,
|
|
438
|
+
};
|
|
439
|
+
const skip = (d) => {
|
|
440
|
+
this.logger.debug('Ticket autopilot: no goal replan', { projectId: project.id, trigger: c.trigger, decision: d });
|
|
441
|
+
return { decision: d };
|
|
442
|
+
};
|
|
443
|
+
const pre = decideReplan({ ...gates, backedOff: false, hasGoal: true });
|
|
444
|
+
if (pre.action === 'skip')
|
|
445
|
+
return skip(pre);
|
|
446
|
+
// Backoff after empty replans: lifted by a new ticket or a goal / OKR
|
|
447
|
+
// change (file times only).
|
|
448
|
+
let backedOff = false;
|
|
449
|
+
if (c.ps.replanBackoff) {
|
|
450
|
+
const changedAt = this.deps.goalChangedAt ? await this.deps.goalChangedAt(project).catch(() => null) : null;
|
|
451
|
+
const state = replanBackoffState(c.ps.replanBackoff, { today, tickets: c.tickets, goalChangedAt: changedAt });
|
|
452
|
+
if (state === 'lifted') {
|
|
453
|
+
this.logger.info('Goal replan backoff lifted (new ticket or goal change)', { projectId: project.id, streak: c.ps.replanBackoff.streak });
|
|
454
|
+
delete c.ps.replanBackoff;
|
|
455
|
+
await this.saveState();
|
|
456
|
+
}
|
|
457
|
+
backedOff = state === 'holds';
|
|
458
|
+
}
|
|
459
|
+
if (backedOff)
|
|
460
|
+
return skip(decideReplan({ ...gates, backedOff, hasGoal: true }));
|
|
461
|
+
const goal = await this.deps.goalOf(project, c.now).catch((err) => {
|
|
462
|
+
this.logger.warn('Could not read the project goal (no replan)', { projectId: project.id, error: err instanceof Error ? err.message : String(err) });
|
|
463
|
+
return null;
|
|
464
|
+
});
|
|
465
|
+
// No goal: the evaluation is what it was before goal replans.
|
|
466
|
+
if (!goal || !goal.text.trim())
|
|
467
|
+
return null;
|
|
468
|
+
const decision = decideReplan({ ...gates, backedOff, hasGoal: true });
|
|
469
|
+
if (decision.action === 'skip')
|
|
470
|
+
return skip(decision);
|
|
471
|
+
const closed = closedTicketsSince(c.tickets, nowMs - C.REPLAN_CLOSED_LOOKBACK_DAYS * 24 * 60 * 60 * 1000, C.REPLAN_MAX_CLOSED_TICKETS);
|
|
472
|
+
const experiments = this.deps.openExperiments ? await this.deps.openExperiments(project).catch(() => []) : [];
|
|
473
|
+
const brief = buildReplanBrief({
|
|
474
|
+
project: { id: project.id, name: project.name },
|
|
475
|
+
goal: goal.text,
|
|
476
|
+
closed,
|
|
477
|
+
lookbackDays: C.REPLAN_CLOSED_LOOKBACK_DAYS,
|
|
478
|
+
experiments,
|
|
479
|
+
members: await this.briefMembers(c.teams, c.tickets, c.idleSession),
|
|
480
|
+
maxInFlightPerMember: c.settings.maxInFlightPerMember,
|
|
481
|
+
now: nowMs,
|
|
482
|
+
});
|
|
483
|
+
const workItem = createWorkItem({
|
|
484
|
+
type: C.REPLAN_WORK_ITEM_TYPE,
|
|
485
|
+
owner: 'team_lead',
|
|
486
|
+
target: c.driver.session,
|
|
487
|
+
title: `Plan next tickets toward the goal: ${project.name}`,
|
|
488
|
+
description: `Nothing is left to triage on ${project.name}. ${REPLAN_ASK}`,
|
|
489
|
+
briefMarkdown: capBrief(brief),
|
|
490
|
+
metadata: {
|
|
491
|
+
kind: C.REPLAN_METADATA_KIND,
|
|
492
|
+
projectId: project.id,
|
|
493
|
+
projectPath: project.path,
|
|
494
|
+
teamId: c.driver.teamId,
|
|
495
|
+
requiresVerification: false,
|
|
496
|
+
trigger: c.trigger,
|
|
497
|
+
goalSources: goal.sources,
|
|
498
|
+
closedTicketIds: closed.map((t) => t.id),
|
|
499
|
+
experimentIds: experiments.map((e) => e.id),
|
|
500
|
+
},
|
|
501
|
+
});
|
|
502
|
+
workItem.createdAt = c.now.toISOString();
|
|
503
|
+
workItem.targetSource = 'assigned';
|
|
504
|
+
const replanTrace = startReplanTrace(project, c.now);
|
|
505
|
+
if (replanTrace)
|
|
506
|
+
workItem.traceId = replanTrace;
|
|
507
|
+
await this.deps.pool.addToPool(workItem);
|
|
508
|
+
const count = replansToday + 1;
|
|
509
|
+
c.ps.replans = { day: today, count, lastAt: nowMs, lastWorkItemId: workItem.id, assessed: false };
|
|
510
|
+
// A replan is autopilot work: it starts the day's run trace. Not an owner touch.
|
|
511
|
+
traceAutopilotAction(project, 'replan', {
|
|
512
|
+
summary: `${project.name}: ${c.driver.session} woken to plan the next tickets toward the goal (nothing left to triage)`,
|
|
513
|
+
outcome: 'queued',
|
|
514
|
+
workItemId: workItem.id,
|
|
515
|
+
session: c.driver.session,
|
|
516
|
+
data: {
|
|
517
|
+
trigger: c.trigger,
|
|
518
|
+
goalSources: goal.sources.join(','),
|
|
519
|
+
closed: closed.length,
|
|
520
|
+
experiments: experiments.length,
|
|
521
|
+
replansToday: count,
|
|
522
|
+
replansPerDay: c.settings.replansPerDay,
|
|
523
|
+
},
|
|
524
|
+
alsoTraceId: workItem.traceId ?? null,
|
|
525
|
+
now: c.now,
|
|
526
|
+
});
|
|
527
|
+
await this.saveState();
|
|
528
|
+
this.logger.info('Ticket autopilot woke the driver to plan toward the goal', {
|
|
529
|
+
projectId: project.id,
|
|
530
|
+
driver: c.driver.session,
|
|
531
|
+
trigger: c.trigger,
|
|
532
|
+
workItemId: workItem.id,
|
|
533
|
+
replansToday: count,
|
|
534
|
+
});
|
|
535
|
+
return { decision, workItem };
|
|
536
|
+
}
|
|
537
|
+
/**
|
|
538
|
+
* The team section of a brief: every member once, with availability,
|
|
539
|
+
* role line and in-progress count.
|
|
540
|
+
*
|
|
541
|
+
* @param teams - Project teams
|
|
542
|
+
* @param tickets - Project tickets (in-progress counts)
|
|
543
|
+
* @param idleSession - The member that just went idle
|
|
544
|
+
* @returns Brief members
|
|
545
|
+
*/
|
|
546
|
+
async briefMembers(teams, tickets, idleSession) {
|
|
547
|
+
const inFlight = inFlightByAssignee(tickets);
|
|
548
|
+
const seen = new Set();
|
|
549
|
+
const roster = teams.flatMap((t) => (t.members ?? [])
|
|
550
|
+
.filter((m) => {
|
|
551
|
+
const s = sessionOf(m);
|
|
552
|
+
if (!s || seen.has(s))
|
|
553
|
+
return false;
|
|
554
|
+
seen.add(s);
|
|
555
|
+
return true;
|
|
556
|
+
})
|
|
557
|
+
.map((m) => ({ team: t, m })));
|
|
558
|
+
const roleLines = await this.roleDescriptions(roster.map(({ m }) => m.role));
|
|
559
|
+
return roster.map(({ team: t, m }) => {
|
|
560
|
+
const session = sessionOf(m);
|
|
561
|
+
// Stopped (idle-stopped, suspended) is "available — started when
|
|
562
|
+
// assigned", never "busy"; its in-progress count is its real one.
|
|
563
|
+
const availability = session === idleSession && memberAvailability(m) !== 'stopped' ? 'idle' : memberAvailability(m);
|
|
564
|
+
return {
|
|
565
|
+
session,
|
|
566
|
+
name: m.name,
|
|
567
|
+
role: m.role,
|
|
568
|
+
lead: isTeamLead(t, m),
|
|
569
|
+
availability,
|
|
570
|
+
responsibility: memberResponsibility(m, roleLines.get(String(m.role ?? ''))),
|
|
571
|
+
inFlight: inFlight.get(session) ?? 0,
|
|
572
|
+
};
|
|
573
|
+
});
|
|
574
|
+
}
|
|
575
|
+
/**
|
|
576
|
+
* Record the outcome of the last goal replan once it is no longer live:
|
|
577
|
+
* a ticket created since it was queued clears the backoff; none ("there
|
|
578
|
+
* are none") backs off one step longer (2, 4, 7 days).
|
|
579
|
+
*
|
|
580
|
+
* @param project - Project
|
|
581
|
+
* @param ps - Its bookkeeping
|
|
582
|
+
* @param tickets - Its tickets
|
|
583
|
+
* @param liveReplanId - The live replan, if any
|
|
584
|
+
* @param nowMs - Clock
|
|
585
|
+
*/
|
|
586
|
+
assessLastReplan(project, ps, tickets, liveReplanId, nowMs) {
|
|
587
|
+
const last = ps.replans;
|
|
588
|
+
if (!last?.lastWorkItemId || last.lastAt === undefined || last.assessed !== false)
|
|
589
|
+
return;
|
|
590
|
+
if (liveReplanId === last.lastWorkItemId)
|
|
591
|
+
return;
|
|
592
|
+
const next = nextReplanBackoff({ replanAt: last.lastAt, replanDay: last.day, tickets, previous: ps.replanBackoff, now: nowMs });
|
|
593
|
+
if (next)
|
|
594
|
+
ps.replanBackoff = next;
|
|
595
|
+
else
|
|
596
|
+
delete ps.replanBackoff;
|
|
597
|
+
last.assessed = true;
|
|
598
|
+
this.logger.info(next ? 'Goal replan opened no tickets: backing off' : 'Goal replan opened tickets', {
|
|
599
|
+
projectId: project.id,
|
|
600
|
+
workItemId: last.lastWorkItemId,
|
|
601
|
+
...(next ? { streak: next.streak, resumeDay: next.resumeDay } : {}),
|
|
602
|
+
});
|
|
603
|
+
}
|
|
604
|
+
/**
|
|
605
|
+
* Expire a goal replan past its TTL so it cannot hold triage forever:
|
|
606
|
+
* cancelled where the pool allows it (queued / blocked / scheduled,
|
|
607
|
+
* running, proposed / accepted / escalated). One the driver already
|
|
608
|
+
* finished (`done_by_worker`) waits on a verdict the autopilot cannot
|
|
609
|
+
* give; it is only no longer counted as live.
|
|
610
|
+
*
|
|
611
|
+
* @param project - Project
|
|
612
|
+
* @param wi - The replan
|
|
613
|
+
* @param ttlHours - Its TTL
|
|
614
|
+
*/
|
|
615
|
+
async expireReplan(project, wi, ttlHours) {
|
|
616
|
+
const reason = `goal replan still open after ${ttlHours}h; expired by the ticket autopilot`;
|
|
617
|
+
const pool = this.deps.pool;
|
|
618
|
+
try {
|
|
619
|
+
if (wi.status === 'queued' || wi.status === 'blocked' || wi.status === 'scheduled') {
|
|
620
|
+
await pool.cancelQueued(wi.id, reason);
|
|
621
|
+
}
|
|
622
|
+
else if (wi.status === 'running') {
|
|
623
|
+
await pool.releaseClaim?.(wi.id, reason);
|
|
624
|
+
await pool.transitionStatus?.(wi.id, 'cancelled', 'system', undefined, reason);
|
|
625
|
+
}
|
|
626
|
+
else if (wi.status === 'proposed' || wi.status === 'accepted' || wi.status === 'escalated') {
|
|
627
|
+
await pool.transitionStatus?.(wi.id, 'cancelled', 'system', undefined, reason);
|
|
628
|
+
}
|
|
629
|
+
else {
|
|
630
|
+
this.logger.debug('Expired goal replan left as is (not cancellable from its status)', { projectId: project.id, workItemId: wi.id, status: wi.status });
|
|
631
|
+
return;
|
|
632
|
+
}
|
|
633
|
+
this.logger.info('Goal replan expired', { projectId: project.id, workItemId: wi.id, status: wi.status, ttlHours });
|
|
634
|
+
}
|
|
635
|
+
catch (err) {
|
|
636
|
+
// Still expired: it no longer holds triage even if the pool refused.
|
|
637
|
+
this.logger.warn('Could not cancel an expired goal replan (no longer counted as live)', {
|
|
638
|
+
projectId: project.id,
|
|
639
|
+
workItemId: wi.id,
|
|
640
|
+
error: err instanceof Error ? err.message : String(err),
|
|
641
|
+
});
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
/**
|
|
645
|
+
* The project's live autopilot item of a kind (triage or goal replan), if
|
|
646
|
+
* any. A triage still queued (never picked up) after
|
|
647
|
+
* {@link TICKET_AUTOPILOT_CONSTANTS.TRIAGE_STALE_QUEUED_MS} is cancelled so
|
|
648
|
+
* a fresh one can replace it. A replan older than its TTL in any live
|
|
649
|
+
* status is expired ({@link expireReplan}) and not returned.
|
|
396
650
|
*
|
|
397
651
|
* @param project - Project
|
|
398
652
|
* @param nowMs - Clock
|
|
653
|
+
* @param kind - `metadata.kind` (triage or replan)
|
|
654
|
+
* @param replanTtlHours - Replan TTL (replan kind only)
|
|
399
655
|
* @returns The live item, or null
|
|
400
656
|
*/
|
|
401
|
-
async
|
|
402
|
-
const items = (await this.deps.pool.getAllItems()).filter((wi) => wi.metadata?.kind ===
|
|
657
|
+
async liveItem(project, nowMs, kind, replanTtlHours) {
|
|
658
|
+
const items = (await this.deps.pool.getAllItems()).filter((wi) => wi.metadata?.kind === kind && wi.metadata?.projectId === project.id && LIVE_STATUSES.has(wi.status));
|
|
659
|
+
const what = kind === TICKET_AUTOPILOT_CONSTANTS.REPLAN_METADATA_KIND ? 'goal replan' : 'ticket triage';
|
|
403
660
|
let live = null;
|
|
404
661
|
for (const wi of items) {
|
|
405
662
|
const age = nowMs - (Date.parse(wi.createdAt) || nowMs);
|
|
406
|
-
if (
|
|
407
|
-
|
|
408
|
-
|
|
663
|
+
if (replanTtlHours !== undefined) {
|
|
664
|
+
if (age >= replanTtlHours * 60 * 60 * 1000) {
|
|
665
|
+
await this.expireReplan(project, wi, replanTtlHours);
|
|
666
|
+
continue;
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
else if (wi.status === 'queued' && age >= TICKET_AUTOPILOT_CONSTANTS.TRIAGE_STALE_QUEUED_MS) {
|
|
670
|
+
await this.deps.pool.cancelQueued(wi.id, `${what} never picked up; replaced by a fresh one`).catch(() => undefined);
|
|
671
|
+
this.logger.info(`Stale ${what} cancelled`, { projectId: project.id, workItemId: wi.id });
|
|
409
672
|
continue;
|
|
410
673
|
}
|
|
411
674
|
live = wi;
|
|
@@ -878,7 +1141,7 @@ export class TicketAutopilotService {
|
|
|
878
1141
|
ps.retroScheduledFor = day;
|
|
879
1142
|
// Only a day with real autopilot work gets a retro (skips and budget
|
|
880
1143
|
// notices alone are not a run).
|
|
881
|
-
if (dayStats.triaged + dayStats.started + dayStats.done + dayStats.verified === 0) {
|
|
1144
|
+
if (dayStats.triaged + dayStats.replans + dayStats.started + dayStats.done + dayStats.verified === 0) {
|
|
882
1145
|
this.logger.debug('Autopilot retro skipped: no autopilot work that day', { projectId: project.id, day });
|
|
883
1146
|
continue;
|
|
884
1147
|
}
|
|
@@ -1178,7 +1441,15 @@ export class TicketAutopilotService {
|
|
|
1178
1441
|
const budget = this.budgetToday(settings, teams);
|
|
1179
1442
|
const state = await this.loadState();
|
|
1180
1443
|
const ps = state.projects[project.id];
|
|
1181
|
-
const
|
|
1444
|
+
const items = await this.deps.pool.getAllItems();
|
|
1445
|
+
const nowMs = this.now().getTime();
|
|
1446
|
+
const ttlMs = settings.replanTtlHours * 60 * 60 * 1000;
|
|
1447
|
+
const liveOf = (kind) => items.some((wi) => wi.metadata?.kind === kind &&
|
|
1448
|
+
wi.metadata?.projectId === project.id &&
|
|
1449
|
+
LIVE_STATUSES.has(wi.status) &&
|
|
1450
|
+
(kind !== TICKET_AUTOPILOT_CONSTANTS.REPLAN_METADATA_KIND || nowMs - (Date.parse(wi.createdAt) || nowMs) < ttlMs));
|
|
1451
|
+
const live = liveOf(TICKET_AUTOPILOT_CONSTANTS.TRIAGE_METADATA_KIND);
|
|
1452
|
+
const today = localDateKey(this.now());
|
|
1182
1453
|
return {
|
|
1183
1454
|
project: { id: project.id, name: project.name, path: project.path },
|
|
1184
1455
|
settings,
|
|
@@ -1189,6 +1460,10 @@ export class TicketAutopilotService {
|
|
|
1189
1460
|
pausedForToday: settings.enabled && spent >= budget.tokens,
|
|
1190
1461
|
triageInFlight: live,
|
|
1191
1462
|
lastTriageAt: ps?.lastTriageAt ? new Date(ps.lastTriageAt).toISOString() : null,
|
|
1463
|
+
replanInFlight: liveOf(TICKET_AUTOPILOT_CONSTANTS.REPLAN_METADATA_KIND),
|
|
1464
|
+
replansToday: ps?.replans?.day === today ? ps.replans.count : 0,
|
|
1465
|
+
lastReplanAt: ps?.replans?.lastAt ? new Date(ps.replans.lastAt).toISOString() : null,
|
|
1466
|
+
replanBackoffUntil: ps?.replanBackoff && today < ps.replanBackoff.resumeDay ? ps.replanBackoff.resumeDay : null,
|
|
1192
1467
|
retroOn: await this.retroOn(project),
|
|
1193
1468
|
};
|
|
1194
1469
|
}
|