crewly 1.20.179 → 1.20.180

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (145) hide show
  1. package/config/roles/team-leader/prompt.md +2 -2
  2. package/config/roles/team-leader/tl-addon.md +2 -2
  3. package/config/skills/agent/core/ask-owner/SKILL.md +79 -0
  4. package/config/skills/agent/core/ask-owner/execute.sh +66 -0
  5. package/config/skills/agent/core/ask-owner/execute.test.sh +62 -0
  6. package/config/skills/agent/core/project-tickets/SKILL.md +21 -9
  7. package/config/skills/agent/core/project-tickets/execute.sh +28 -9
  8. package/config/skills/agent/core/project-tickets/execute.test.sh +12 -4
  9. package/config/skills/agent/core/reply/SKILL.md +22 -5
  10. package/config/skills/agent/core/reply/execute.sh +12 -5
  11. package/config/skills/agent/core/reply/execute.test.sh +8 -0
  12. package/config/skills/agent/core/schedule-followup/SKILL.md +8 -0
  13. package/config/skills/agent/core/schedule-followup/execute.sh +7 -0
  14. package/config/slack-app-manifest.json +5 -1
  15. package/config/slack-master-app-manifest.json +7 -2
  16. package/dist/backend/backend/src/constants.d.ts +76 -5
  17. package/dist/backend/backend/src/constants.d.ts.map +1 -1
  18. package/dist/backend/backend/src/constants.js +83 -5
  19. package/dist/backend/backend/src/constants.js.map +1 -1
  20. package/dist/backend/backend/src/controllers/chat/agent-reply.controller.d.ts +3 -0
  21. package/dist/backend/backend/src/controllers/chat/agent-reply.controller.d.ts.map +1 -1
  22. package/dist/backend/backend/src/controllers/chat/agent-reply.controller.js +54 -1
  23. package/dist/backend/backend/src/controllers/chat/agent-reply.controller.js.map +1 -1
  24. package/dist/backend/backend/src/controllers/decisions/decisions.controller.d.ts +67 -0
  25. package/dist/backend/backend/src/controllers/decisions/decisions.controller.d.ts.map +1 -0
  26. package/dist/backend/backend/src/controllers/decisions/decisions.controller.js +215 -0
  27. package/dist/backend/backend/src/controllers/decisions/decisions.controller.js.map +1 -0
  28. package/dist/backend/backend/src/controllers/project-tickets/project-tickets.controller.d.ts +8 -5
  29. package/dist/backend/backend/src/controllers/project-tickets/project-tickets.controller.d.ts.map +1 -1
  30. package/dist/backend/backend/src/controllers/project-tickets/project-tickets.controller.js +51 -12
  31. package/dist/backend/backend/src/controllers/project-tickets/project-tickets.controller.js.map +1 -1
  32. package/dist/backend/backend/src/index.d.ts +4 -0
  33. package/dist/backend/backend/src/index.d.ts.map +1 -1
  34. package/dist/backend/backend/src/index.js +88 -2
  35. package/dist/backend/backend/src/index.js.map +1 -1
  36. package/dist/backend/backend/src/routes/api.routes.d.ts.map +1 -1
  37. package/dist/backend/backend/src/routes/api.routes.js +6 -0
  38. package/dist/backend/backend/src/routes/api.routes.js.map +1 -1
  39. package/dist/backend/backend/src/services/boot/boot-announce.service.d.ts +1 -1
  40. package/dist/backend/backend/src/services/boot/boot-announce.service.js +13 -13
  41. package/dist/backend/backend/src/services/boot/boot-announce.service.js.map +1 -1
  42. package/dist/backend/backend/src/services/cloud/cloud-disconnect-notice.service.js +6 -6
  43. package/dist/backend/backend/src/services/cloud/cloud-disconnect-notice.service.js.map +1 -1
  44. package/dist/backend/backend/src/services/cloud/cloud-disconnect-notice.utils.d.ts +3 -3
  45. package/dist/backend/backend/src/services/cloud/cloud-disconnect-notice.utils.d.ts.map +1 -1
  46. package/dist/backend/backend/src/services/cloud/cloud-disconnect-notice.utils.js +14 -13
  47. package/dist/backend/backend/src/services/cloud/cloud-disconnect-notice.utils.js.map +1 -1
  48. package/dist/backend/backend/src/services/decisions/decision-card.d.ts +147 -0
  49. package/dist/backend/backend/src/services/decisions/decision-card.d.ts.map +1 -0
  50. package/dist/backend/backend/src/services/decisions/decision-card.js +311 -0
  51. package/dist/backend/backend/src/services/decisions/decision-card.js.map +1 -0
  52. package/dist/backend/backend/src/services/decisions/decision-contract.d.ts +83 -0
  53. package/dist/backend/backend/src/services/decisions/decision-contract.d.ts.map +1 -0
  54. package/dist/backend/backend/src/services/decisions/decision-contract.js +226 -0
  55. package/dist/backend/backend/src/services/decisions/decision-contract.js.map +1 -0
  56. package/dist/backend/backend/src/services/decisions/decision-routing.d.ts +34 -0
  57. package/dist/backend/backend/src/services/decisions/decision-routing.d.ts.map +1 -0
  58. package/dist/backend/backend/src/services/decisions/decision-routing.js +64 -0
  59. package/dist/backend/backend/src/services/decisions/decision-routing.js.map +1 -0
  60. package/dist/backend/backend/src/services/decisions/decision-store.d.ts +80 -0
  61. package/dist/backend/backend/src/services/decisions/decision-store.d.ts.map +1 -0
  62. package/dist/backend/backend/src/services/decisions/decision-store.js +151 -0
  63. package/dist/backend/backend/src/services/decisions/decision-store.js.map +1 -0
  64. package/dist/backend/backend/src/services/decisions/decision.service.d.ts +281 -0
  65. package/dist/backend/backend/src/services/decisions/decision.service.d.ts.map +1 -0
  66. package/dist/backend/backend/src/services/decisions/decision.service.js +639 -0
  67. package/dist/backend/backend/src/services/decisions/decision.service.js.map +1 -0
  68. package/dist/backend/backend/src/services/decisions/decision.wiring.d.ts +70 -0
  69. package/dist/backend/backend/src/services/decisions/decision.wiring.d.ts.map +1 -0
  70. package/dist/backend/backend/src/services/decisions/decision.wiring.js +172 -0
  71. package/dist/backend/backend/src/services/decisions/decision.wiring.js.map +1 -0
  72. package/dist/backend/backend/src/services/decisions/ticket-thread-store.d.ts +97 -0
  73. package/dist/backend/backend/src/services/decisions/ticket-thread-store.d.ts.map +1 -0
  74. package/dist/backend/backend/src/services/decisions/ticket-thread-store.js +137 -0
  75. package/dist/backend/backend/src/services/decisions/ticket-thread-store.js.map +1 -0
  76. package/dist/backend/backend/src/services/orc/work-item-destination.d.ts +165 -0
  77. package/dist/backend/backend/src/services/orc/work-item-destination.d.ts.map +1 -0
  78. package/dist/backend/backend/src/services/orc/work-item-destination.js +239 -0
  79. package/dist/backend/backend/src/services/orc/work-item-destination.js.map +1 -0
  80. package/dist/backend/backend/src/services/orc/work-item-destination.wiring.d.ts +134 -0
  81. package/dist/backend/backend/src/services/orc/work-item-destination.wiring.d.ts.map +1 -0
  82. package/dist/backend/backend/src/services/orc/work-item-destination.wiring.js +217 -0
  83. package/dist/backend/backend/src/services/orc/work-item-destination.wiring.js.map +1 -0
  84. package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-decision.d.ts +1 -30
  85. package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-decision.d.ts.map +1 -1
  86. package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-decision.js +1 -22
  87. package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-decision.js.map +1 -1
  88. package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-messages.d.ts +2 -15
  89. package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-messages.d.ts.map +1 -1
  90. package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-messages.js +8 -22
  91. package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-messages.js.map +1 -1
  92. package/dist/backend/backend/src/services/project-tickets/ticket-autopilot.service.d.ts +11 -12
  93. package/dist/backend/backend/src/services/project-tickets/ticket-autopilot.service.d.ts.map +1 -1
  94. package/dist/backend/backend/src/services/project-tickets/ticket-autopilot.service.js +27 -52
  95. package/dist/backend/backend/src/services/project-tickets/ticket-autopilot.service.js.map +1 -1
  96. package/dist/backend/backend/src/services/slack/slack-agent-post.service.d.ts +6 -0
  97. package/dist/backend/backend/src/services/slack/slack-agent-post.service.d.ts.map +1 -1
  98. package/dist/backend/backend/src/services/slack/slack-agent-post.service.js +3 -1
  99. package/dist/backend/backend/src/services/slack/slack-agent-post.service.js.map +1 -1
  100. package/dist/backend/backend/src/services/slack/slack-team-channel.service.js +12 -12
  101. package/dist/backend/backend/src/services/slack/slack-team-channel.service.js.map +1 -1
  102. package/dist/backend/backend/src/services/slack/slack.service.d.ts +16 -1
  103. package/dist/backend/backend/src/services/slack/slack.service.d.ts.map +1 -1
  104. package/dist/backend/backend/src/services/slack/slack.service.js +56 -2
  105. package/dist/backend/backend/src/services/slack/slack.service.js.map +1 -1
  106. package/dist/backend/backend/src/services/system/auto-update.service.js +2 -2
  107. package/dist/backend/backend/src/services/system/auto-update.service.js.map +1 -1
  108. package/dist/backend/backend/src/services/system/auto-update.utils.js +2 -2
  109. package/dist/backend/backend/src/services/system/auto-update.utils.js.map +1 -1
  110. package/dist/backend/backend/src/types/decision.types.d.ts +139 -0
  111. package/dist/backend/backend/src/types/decision.types.d.ts.map +1 -0
  112. package/dist/backend/backend/src/types/decision.types.js +13 -0
  113. package/dist/backend/backend/src/types/decision.types.js.map +1 -0
  114. package/dist/backend/backend/src/types/slack.types.d.ts +30 -0
  115. package/dist/backend/backend/src/types/slack.types.d.ts.map +1 -1
  116. package/dist/backend/backend/src/types/slack.types.js.map +1 -1
  117. package/dist/backend/backend/src/types/v2/trigger.types.d.ts +17 -0
  118. package/dist/backend/backend/src/types/v2/trigger.types.d.ts.map +1 -1
  119. package/dist/backend/backend/src/types/v2/trigger.types.js +17 -0
  120. package/dist/backend/backend/src/types/v2/trigger.types.js.map +1 -1
  121. package/dist/backend/build-info.json +2 -2
  122. package/dist/cli/backend/src/constants.d.ts +76 -5
  123. package/dist/cli/backend/src/constants.d.ts.map +1 -1
  124. package/dist/cli/backend/src/constants.js +83 -5
  125. package/dist/cli/backend/src/constants.js.map +1 -1
  126. package/dist/cli/backend/src/services/slack/slack-team-channel.service.js +12 -12
  127. package/dist/cli/backend/src/services/slack/slack-team-channel.service.js.map +1 -1
  128. package/dist/cli/backend/src/services/slack/slack.service.d.ts +16 -1
  129. package/dist/cli/backend/src/services/slack/slack.service.d.ts.map +1 -1
  130. package/dist/cli/backend/src/services/slack/slack.service.js +56 -2
  131. package/dist/cli/backend/src/services/slack/slack.service.js.map +1 -1
  132. package/dist/cli/backend/src/services/system/auto-update.utils.js +2 -2
  133. package/dist/cli/backend/src/services/system/auto-update.utils.js.map +1 -1
  134. package/dist/cli/backend/src/types/slack.types.d.ts +30 -0
  135. package/dist/cli/backend/src/types/slack.types.d.ts.map +1 -1
  136. package/dist/cli/backend/src/types/slack.types.js.map +1 -1
  137. package/dist/cli/backend/src/types/v2/trigger.types.d.ts +17 -0
  138. package/dist/cli/backend/src/types/v2/trigger.types.d.ts.map +1 -1
  139. package/dist/cli/backend/src/types/v2/trigger.types.js +17 -0
  140. package/dist/cli/backend/src/types/v2/trigger.types.js.map +1 -1
  141. package/frontend/dist/assets/index-04d192f8.css +42 -0
  142. package/frontend/dist/assets/{index-69af46c3.js → index-4271e95e.js} +259 -259
  143. package/frontend/dist/index.html +2 -2
  144. package/package.json +1 -1
  145. package/frontend/dist/assets/index-cdc52122.css +0 -42
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Who asks the owner about a ticket (specs/2026-10-01-decision-cards.md §2). Pure.
3
+ *
4
+ * The responsible agent asks, never the orchestrator: the ticket's assignee
5
+ * when it is a member of one of the project's teams, else the lead of the
6
+ * ticket's team, else the lead of the first project team.
7
+ *
8
+ * @module services/decisions/decision-routing
9
+ */
10
+ import type { Team } from '../../types/index.js';
11
+ import type { ProjectTicket } from '../../types/project-ticket.types.js';
12
+ /** The chosen asker. */
13
+ export interface TicketAsker {
14
+ session: string;
15
+ teamId: string;
16
+ why: 'assignee' | 'ticket_team_lead' | 'project_team_lead';
17
+ }
18
+ /**
19
+ * Pick the agent that asks the owner about a ticket.
20
+ *
21
+ * @param ticket - Ticket (assignee, team)
22
+ * @param teams - The project's non-archived teams
23
+ * @returns The asker, or null when the project has no team with a lead
24
+ */
25
+ export declare function pickTicketAsker(ticket: Pick<ProjectTicket, 'assignee' | 'team'>, teams: Team[]): TicketAsker | null;
26
+ /**
27
+ * The team an agent session belongs to.
28
+ *
29
+ * @param session - Agent session
30
+ * @param teams - Teams
31
+ * @returns Team id, or undefined
32
+ */
33
+ export declare function teamOfSession(session: string, teams: Team[]): string | undefined;
34
+ //# sourceMappingURL=decision-routing.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decision-routing.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/decisions/decision-routing.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,KAAK,EAAE,IAAI,EAAc,MAAM,sBAAsB,CAAC;AAC7D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qCAAqC,CAAC;AAGzE,wBAAwB;AACxB,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,UAAU,GAAG,kBAAkB,GAAG,mBAAmB,CAAC;CAC5D;AAYD;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,IAAI,CAAC,aAAa,EAAE,UAAU,GAAG,MAAM,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,WAAW,GAAG,IAAI,CAkBnH;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,MAAM,GAAG,SAAS,CAKhF"}
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Who asks the owner about a ticket (specs/2026-10-01-decision-cards.md §2). Pure.
3
+ *
4
+ * The responsible agent asks, never the orchestrator: the ticket's assignee
5
+ * when it is a member of one of the project's teams, else the lead of the
6
+ * ticket's team, else the lead of the first project team.
7
+ *
8
+ * @module services/decisions/decision-routing
9
+ */
10
+ import { ORCHESTRATOR_SESSION_NAME } from '../../constants.js';
11
+ import { getTeamLeads } from '../../utils/team.utils.js';
12
+ /**
13
+ * A member's session name.
14
+ *
15
+ * @param m - Member
16
+ * @returns Session (falls back to the member id)
17
+ */
18
+ function sessionOf(m) {
19
+ return m.sessionName || m.id;
20
+ }
21
+ /**
22
+ * Pick the agent that asks the owner about a ticket.
23
+ *
24
+ * @param ticket - Ticket (assignee, team)
25
+ * @param teams - The project's non-archived teams
26
+ * @returns The asker, or null when the project has no team with a lead
27
+ */
28
+ export function pickTicketAsker(ticket, teams) {
29
+ const assignee = ticket.assignee?.trim();
30
+ if (assignee && assignee !== ORCHESTRATOR_SESSION_NAME) {
31
+ for (const team of teams) {
32
+ const m = (team.members ?? []).find((x) => x.sessionName === assignee || x.agentId === assignee || x.id === assignee);
33
+ if (m)
34
+ return { session: sessionOf(m), teamId: team.id, why: 'assignee' };
35
+ }
36
+ }
37
+ const own = ticket.team ? teams.find((t) => t.id === ticket.team) : undefined;
38
+ if (own) {
39
+ const lead = getTeamLeads(own)[0];
40
+ if (lead)
41
+ return { session: sessionOf(lead), teamId: own.id, why: 'ticket_team_lead' };
42
+ }
43
+ for (const team of teams) {
44
+ const lead = getTeamLeads(team)[0];
45
+ if (lead)
46
+ return { session: sessionOf(lead), teamId: team.id, why: 'project_team_lead' };
47
+ }
48
+ return null;
49
+ }
50
+ /**
51
+ * The team an agent session belongs to.
52
+ *
53
+ * @param session - Agent session
54
+ * @param teams - Teams
55
+ * @returns Team id, or undefined
56
+ */
57
+ export function teamOfSession(session, teams) {
58
+ for (const team of teams) {
59
+ if ((team.members ?? []).some((m) => m.sessionName === session || m.agentId === session))
60
+ return team.id;
61
+ }
62
+ return undefined;
63
+ }
64
+ //# sourceMappingURL=decision-routing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decision-routing.js","sourceRoot":"","sources":["../../../../../../backend/src/services/decisions/decision-routing.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,yBAAyB,EAAE,MAAM,oBAAoB,CAAC;AAG/D,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AASzD;;;;;GAKG;AACH,SAAS,SAAS,CAAC,CAAa;IAC9B,OAAO,CAAC,CAAC,WAAW,IAAI,CAAC,CAAC,EAAE,CAAC;AAC/B,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,eAAe,CAAC,MAAgD,EAAE,KAAa;IAC7F,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC;IACzC,IAAI,QAAQ,IAAI,QAAQ,KAAK,yBAAyB,EAAE,CAAC;QACvD,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,KAAK,QAAQ,IAAI,CAAC,CAAC,OAAO,KAAK,QAAQ,IAAI,CAAC,CAAC,EAAE,KAAK,QAAQ,CAAC,CAAC;YACtH,IAAI,CAAC;gBAAE,OAAO,EAAE,OAAO,EAAE,SAAS,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,EAAE,GAAG,EAAE,UAAU,EAAE,CAAC;QAC5E,CAAC;IACH,CAAC;IACD,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAC9E,IAAI,GAAG,EAAE,CAAC;QACR,MAAM,IAAI,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QAClC,IAAI,IAAI;YAAE,OAAO,EAAE,OAAO,EAAE,SAAS,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,EAAE,GAAG,EAAE,kBAAkB,EAAE,CAAC;IACzF,CAAC;IACD,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,IAAI,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;QACnC,IAAI,IAAI;YAAE,OAAO,EAAE,OAAO,EAAE,SAAS,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,EAAE,GAAG,EAAE,mBAAmB,EAAE,CAAC;IAC3F,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,aAAa,CAAC,OAAe,EAAE,KAAa;IAC1D,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,CAAC,IAAI,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,KAAK,OAAO,IAAI,CAAC,CAAC,OAAO,KAAK,OAAO,CAAC;YAAE,OAAO,IAAI,CAAC,EAAE,CAAC;IAC3G,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC"}
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Durable store of owner decisions (specs/2026-10-01-decision-cards.md §1).
3
+ *
4
+ * One JSON file under CREWLY_HOME with a monotonically increasing id
5
+ * counter, so `D-7` is never reused. All writes are serialised and atomic.
6
+ * Settled decisions are pruned after {@link DECISION_CONSTANTS.RESOLVED_KEEP_MS}.
7
+ *
8
+ * @module services/decisions/decision-store
9
+ */
10
+ import type { OwnerDecision } from '../../types/decision.types.js';
11
+ /** Statuses still in front of the owner. */
12
+ export declare const PENDING_DECISION_STATUSES: ReadonlySet<OwnerDecision['status']>;
13
+ /**
14
+ * Persistent decision store.
15
+ */
16
+ export declare class DecisionStore {
17
+ private readonly filePath;
18
+ private readonly now;
19
+ private data;
20
+ private chain;
21
+ /**
22
+ * @param filePath - JSON file
23
+ * @param now - Clock (tests)
24
+ */
25
+ constructor(filePath: string, now?: () => Date);
26
+ /**
27
+ * Store under a CREWLY_HOME.
28
+ *
29
+ * @param crewlyHome - CREWLY_HOME
30
+ * @returns The store
31
+ */
32
+ static inHome(crewlyHome: string): DecisionStore;
33
+ /**
34
+ * Add a decision, assigning its id.
35
+ *
36
+ * @param draft - Decision without id/timestamps
37
+ * @returns The stored decision
38
+ */
39
+ create(draft: Omit<OwnerDecision, 'id' | 'createdAt' | 'updatedAt'>): Promise<OwnerDecision>;
40
+ /**
41
+ * Change a decision under the write lock.
42
+ *
43
+ * @param id - Decision id
44
+ * @param fn - Returns the patch, or null to leave it alone
45
+ * @returns The updated decision, or null when missing / unchanged
46
+ */
47
+ update(id: string, fn: (d: OwnerDecision) => Partial<OwnerDecision> | null): Promise<OwnerDecision | null>;
48
+ /**
49
+ * One decision.
50
+ *
51
+ * @param id - Decision id
52
+ * @returns Copy, or null
53
+ */
54
+ get(id: string): Promise<OwnerDecision | null>;
55
+ /**
56
+ * All decisions, newest first, optionally filtered.
57
+ *
58
+ * @param filter - Predicate
59
+ * @returns Copies
60
+ */
61
+ list(filter?: (d: OwnerDecision) => boolean): Promise<OwnerDecision[]>;
62
+ /**
63
+ * The open decision whose card is this message (or whose thread this is).
64
+ *
65
+ * @param slackChannelId - Channel
66
+ * @param ts - Card ts or thread ts
67
+ * @returns Decision, or null
68
+ */
69
+ findByCard(slackChannelId: string, ts: string): Promise<OwnerDecision | null>;
70
+ /**
71
+ * Drop settled decisions older than the keep window.
72
+ *
73
+ * @returns How many were dropped
74
+ */
75
+ prune(): Promise<number>;
76
+ private load;
77
+ private save;
78
+ private serial;
79
+ }
80
+ //# sourceMappingURL=decision-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decision-store.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/decisions/decision-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAKH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AAQnE,4CAA4C;AAC5C,eAAO,MAAM,yBAAyB,EAAE,WAAW,CAAC,aAAa,CAAC,QAAQ,CAAC,CAA+B,CAAC;AAE3G;;GAEG;AACH,qBAAa,aAAa;IAStB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,GAAG;IATtB,OAAO,CAAC,IAAI,CAA6B;IACzC,OAAO,CAAC,KAAK,CAAuC;IAEpD;;;OAGG;gBAEgB,QAAQ,EAAE,MAAM,EAChB,GAAG,GAAE,MAAM,IAAuB;IAGrD;;;;;OAKG;IACH,MAAM,CAAC,MAAM,CAAC,UAAU,EAAE,MAAM,GAAG,aAAa;IAIhD;;;;;OAKG;IACG,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC,aAAa,EAAE,IAAI,GAAG,WAAW,GAAG,WAAW,CAAC,GAAG,OAAO,CAAC,aAAa,CAAC;IAYlG;;;;;;OAMG;IACG,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,aAAa,KAAK,OAAO,CAAC,aAAa,CAAC,GAAG,IAAI,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC;IAchH;;;;;OAKG;IACG,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC;IAMpD;;;;;OAKG;IACG,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,EAAE,aAAa,KAAK,OAAO,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC;IAQ5E;;;;;;OAMG;IACG,UAAU,CAAC,cAAc,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC;IAMnF;;;;OAIG;IACG,KAAK,IAAI,OAAO,CAAC,MAAM,CAAC;YAYhB,IAAI;YASJ,IAAI;IAIlB,OAAO,CAAC,MAAM;CAKf"}
@@ -0,0 +1,151 @@
1
+ /**
2
+ * Durable store of owner decisions (specs/2026-10-01-decision-cards.md §1).
3
+ *
4
+ * One JSON file under CREWLY_HOME with a monotonically increasing id
5
+ * counter, so `D-7` is never reused. All writes are serialised and atomic.
6
+ * Settled decisions are pruned after {@link DECISION_CONSTANTS.RESOLVED_KEEP_MS}.
7
+ *
8
+ * @module services/decisions/decision-store
9
+ */
10
+ import * as path from 'path';
11
+ import { DECISION_CONSTANTS } from '../../constants.js';
12
+ import { atomicWriteJson, safeReadJson } from '../../utils/file-io.utils.js';
13
+ /** Statuses still in front of the owner. */
14
+ export const PENDING_DECISION_STATUSES = new Set(['open', 'parked']);
15
+ /**
16
+ * Persistent decision store.
17
+ */
18
+ export class DecisionStore {
19
+ filePath;
20
+ now;
21
+ data = null;
22
+ chain = Promise.resolve();
23
+ /**
24
+ * @param filePath - JSON file
25
+ * @param now - Clock (tests)
26
+ */
27
+ constructor(filePath, now = () => new Date()) {
28
+ this.filePath = filePath;
29
+ this.now = now;
30
+ }
31
+ /**
32
+ * Store under a CREWLY_HOME.
33
+ *
34
+ * @param crewlyHome - CREWLY_HOME
35
+ * @returns The store
36
+ */
37
+ static inHome(crewlyHome) {
38
+ return new DecisionStore(path.join(crewlyHome, DECISION_CONSTANTS.STORE_FILENAME));
39
+ }
40
+ /**
41
+ * Add a decision, assigning its id.
42
+ *
43
+ * @param draft - Decision without id/timestamps
44
+ * @returns The stored decision
45
+ */
46
+ async create(draft) {
47
+ return this.serial(async () => {
48
+ const data = await this.load();
49
+ const at = this.now().toISOString();
50
+ const decision = { ...draft, id: `${DECISION_CONSTANTS.ID_PREFIX}${data.nextId}`, createdAt: at, updatedAt: at };
51
+ data.nextId += 1;
52
+ data.decisions.push(decision);
53
+ await this.save();
54
+ return { ...decision };
55
+ });
56
+ }
57
+ /**
58
+ * Change a decision under the write lock.
59
+ *
60
+ * @param id - Decision id
61
+ * @param fn - Returns the patch, or null to leave it alone
62
+ * @returns The updated decision, or null when missing / unchanged
63
+ */
64
+ async update(id, fn) {
65
+ return this.serial(async () => {
66
+ const data = await this.load();
67
+ const idx = data.decisions.findIndex((d) => d.id === id);
68
+ if (idx < 0)
69
+ return null;
70
+ const patch = fn({ ...data.decisions[idx] });
71
+ if (!patch)
72
+ return null;
73
+ const next = { ...data.decisions[idx], ...patch, updatedAt: this.now().toISOString() };
74
+ data.decisions[idx] = next;
75
+ await this.save();
76
+ return { ...next };
77
+ });
78
+ }
79
+ /**
80
+ * One decision.
81
+ *
82
+ * @param id - Decision id
83
+ * @returns Copy, or null
84
+ */
85
+ async get(id) {
86
+ const data = await this.load();
87
+ const d = data.decisions.find((x) => x.id === id);
88
+ return d ? { ...d } : null;
89
+ }
90
+ /**
91
+ * All decisions, newest first, optionally filtered.
92
+ *
93
+ * @param filter - Predicate
94
+ * @returns Copies
95
+ */
96
+ async list(filter) {
97
+ const data = await this.load();
98
+ return data.decisions
99
+ .filter((d) => !filter || filter(d))
100
+ .map((d) => ({ ...d }))
101
+ .sort((a, b) => Date.parse(b.createdAt) - Date.parse(a.createdAt) || b.id.localeCompare(a.id, undefined, { numeric: true }));
102
+ }
103
+ /**
104
+ * The open decision whose card is this message (or whose thread this is).
105
+ *
106
+ * @param slackChannelId - Channel
107
+ * @param ts - Card ts or thread ts
108
+ * @returns Decision, or null
109
+ */
110
+ async findByCard(slackChannelId, ts) {
111
+ const data = await this.load();
112
+ const d = data.decisions.find((x) => x.card?.slackChannelId === slackChannelId && x.card.messageTs === ts);
113
+ return d ? { ...d } : null;
114
+ }
115
+ /**
116
+ * Drop settled decisions older than the keep window.
117
+ *
118
+ * @returns How many were dropped
119
+ */
120
+ async prune() {
121
+ return this.serial(async () => {
122
+ const data = await this.load();
123
+ const cutoff = this.now().getTime() - DECISION_CONSTANTS.RESOLVED_KEEP_MS;
124
+ const before = data.decisions.length;
125
+ data.decisions = data.decisions.filter((d) => PENDING_DECISION_STATUSES.has(d.status) || Date.parse(d.updatedAt) >= cutoff);
126
+ const dropped = before - data.decisions.length;
127
+ if (dropped > 0)
128
+ await this.save();
129
+ return dropped;
130
+ });
131
+ }
132
+ async load() {
133
+ if (this.data)
134
+ return this.data;
135
+ const raw = await safeReadJson(this.filePath, {});
136
+ const decisions = Array.isArray(raw.decisions) ? raw.decisions : [];
137
+ const maxId = decisions.reduce((m, d) => Math.max(m, Number(String(d.id).replace(DECISION_CONSTANTS.ID_PREFIX, '')) || 0), 0);
138
+ this.data = { nextId: Math.max(Number(raw.nextId) || 1, maxId + 1), decisions };
139
+ return this.data;
140
+ }
141
+ async save() {
142
+ if (this.data)
143
+ await atomicWriteJson(this.filePath, this.data);
144
+ }
145
+ serial(fn) {
146
+ const next = this.chain.then(fn, fn);
147
+ this.chain = next.catch(() => undefined);
148
+ return next;
149
+ }
150
+ }
151
+ //# sourceMappingURL=decision-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decision-store.js","sourceRoot":"","sources":["../../../../../../backend/src/services/decisions/decision-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAC7B,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAS7E,4CAA4C;AAC5C,MAAM,CAAC,MAAM,yBAAyB,GAAyC,IAAI,GAAG,CAAC,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC;AAE3G;;GAEG;AACH,MAAM,OAAO,aAAa;IASL;IACA;IATX,IAAI,GAAwB,IAAI,CAAC;IACjC,KAAK,GAAqB,OAAO,CAAC,OAAO,EAAE,CAAC;IAEpD;;;OAGG;IACH,YACmB,QAAgB,EAChB,MAAkB,GAAG,EAAE,CAAC,IAAI,IAAI,EAAE;QADlC,aAAQ,GAAR,QAAQ,CAAQ;QAChB,QAAG,GAAH,GAAG,CAA+B;IAClD,CAAC;IAEJ;;;;;OAKG;IACH,MAAM,CAAC,MAAM,CAAC,UAAkB;QAC9B,OAAO,IAAI,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,kBAAkB,CAAC,cAAc,CAAC,CAAC,CAAC;IACrF,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,MAAM,CAAC,KAA4D;QACvE,OAAO,IAAI,CAAC,MAAM,CAAC,KAAK,IAAI,EAAE;YAC5B,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC;YAC/B,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,WAAW,EAAE,CAAC;YACpC,MAAM,QAAQ,GAAkB,EAAE,GAAG,KAAK,EAAE,EAAE,EAAE,GAAG,kBAAkB,CAAC,SAAS,GAAG,IAAI,CAAC,MAAM,EAAE,EAAE,SAAS,EAAE,EAAE,EAAE,SAAS,EAAE,EAAE,EAAE,CAAC;YAChI,IAAI,CAAC,MAAM,IAAI,CAAC,CAAC;YACjB,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YAC9B,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC;YAClB,OAAO,EAAE,GAAG,QAAQ,EAAE,CAAC;QACzB,CAAC,CAAC,CAAC;IACL,CAAC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,MAAM,CAAC,EAAU,EAAE,EAAuD;QAC9E,OAAO,IAAI,CAAC,MAAM,CAAC,KAAK,IAAI,EAAE;YAC5B,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC;YAC/B,MAAM,GAAG,GAAG,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;YACzD,IAAI,GAAG,GAAG,CAAC;gBAAE,OAAO,IAAI,CAAC;YACzB,MAAM,KAAK,GAAG,EAAE,CAAC,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YAC7C,IAAI,CAAC,KAAK;gBAAE,OAAO,IAAI,CAAC;YACxB,MAAM,IAAI,GAAkB,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,GAAG,KAAK,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC,WAAW,EAAE,EAAE,CAAC;YACtG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC;YAC3B,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC;YAClB,OAAO,EAAE,GAAG,IAAI,EAAE,CAAC;QACrB,CAAC,CAAC,CAAC;IACL,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,GAAG,CAAC,EAAU;QAClB,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC;QAC/B,MAAM,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;QAClD,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;IAC7B,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,IAAI,CAAC,MAAsC;QAC/C,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC;QAC/B,OAAO,IAAI,CAAC,SAAS;aAClB,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,MAAM,IAAI,MAAM,CAAC,CAAC,CAAC,CAAC;aACnC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC;aACtB,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,EAAE,SAAS,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IACjI,CAAC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,UAAU,CAAC,cAAsB,EAAE,EAAU;QACjD,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC;QAC/B,MAAM,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,cAAc,KAAK,cAAc,IAAI,CAAC,CAAC,IAAI,CAAC,SAAS,KAAK,EAAE,CAAC,CAAC;QAC3G,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;IAC7B,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,KAAK;QACT,OAAO,IAAI,CAAC,MAAM,CAAC,KAAK,IAAI,EAAE;YAC5B,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC;YAC/B,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,OAAO,EAAE,GAAG,kBAAkB,CAAC,gBAAgB,CAAC;YAC1E,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC;YACrC,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,yBAAyB,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,IAAI,MAAM,CAAC,CAAC;YAC5H,MAAM,OAAO,GAAG,MAAM,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC;YAC/C,IAAI,OAAO,GAAG,CAAC;gBAAE,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC;YACnC,OAAO,OAAO,CAAC;QACjB,CAAC,CAAC,CAAC;IACL,CAAC;IAEO,KAAK,CAAC,IAAI;QAChB,IAAI,IAAI,CAAC,IAAI;YAAE,OAAO,IAAI,CAAC,IAAI,CAAC;QAChC,MAAM,GAAG,GAAG,MAAM,YAAY,CAAwB,IAAI,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;QACzE,MAAM,SAAS,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC;QACpE,MAAM,KAAK,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,kBAAkB,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QAC9H,IAAI,CAAC,IAAI,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,KAAK,GAAG,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC;QAChF,OAAO,IAAI,CAAC,IAAI,CAAC;IACnB,CAAC;IAEO,KAAK,CAAC,IAAI;QAChB,IAAI,IAAI,CAAC,IAAI;YAAE,MAAM,eAAe,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;IACjE,CAAC;IAEO,MAAM,CAAI,EAAoB;QACpC,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;QACrC,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;QACzC,OAAO,IAAI,CAAC;IACd,CAAC;CACF"}
@@ -0,0 +1,281 @@
1
+ /**
2
+ * Decision cards (specs/2026-10-01-decision-cards.md).
3
+ *
4
+ * The responsible agent asks the owner ONE structured question; its own
5
+ * Slack bot posts a Block Kit card in the team channel (the ticket's thread,
6
+ * or a new thread), and the owner answers with a button, a reaction, a
7
+ * thread reply or the dashboard. The answer updates the card in place, goes
8
+ * into the ticket log, is delivered to the asking agent and clears the
9
+ * owner-message watchdog for that thread. At the deadline the default is
10
+ * applied — except for sensitive asks, which are re-asked once and parked.
11
+ *
12
+ * Every collaborator is injected ({@link DecisionServiceDeps}); the wiring
13
+ * with the real Slack / tickets / agents lives in `decision.wiring.ts`.
14
+ *
15
+ * @module services/decisions/decision.service
16
+ */
17
+ import { type ComponentLogger } from '../core/logger.service.js';
18
+ import type { SlackBlock, SlackIncomingMessage, SlackOutgoingMessage } from '../../types/slack.types.js';
19
+ import type { AskOwnerInput, OwnerDecision } from '../../types/decision.types.js';
20
+ import { formatWhen } from './decision-card.js';
21
+ import { DecisionStore } from './decision-store.js';
22
+ import type { TicketThreadStore } from './ticket-thread-store.js';
23
+ /** Thrown for requests the caller must fix (HTTP 4xx). */
24
+ export declare class DecisionError extends Error {
25
+ readonly status: number;
26
+ constructor(status: number, message: string);
27
+ }
28
+ /** The slice of SlackService used here. */
29
+ export interface DecisionSlackApi {
30
+ isConnected(): boolean;
31
+ sendMessage(message: SlackOutgoingMessage): Promise<string>;
32
+ updateMessage(channelId: string, messageTs: string, text: string, blocks?: SlackBlock[], botToken?: string): Promise<void>;
33
+ }
34
+ /** How an agent posts: its own bot token, else the shared bot with its name/icon. */
35
+ export interface DecisionPostIdentity {
36
+ botToken?: string;
37
+ username?: string;
38
+ iconEmoji?: string;
39
+ iconUrl?: string;
40
+ }
41
+ /** A ticket the question is about, resolved by the wiring. */
42
+ export interface DecisionTicketContext {
43
+ projectId: string;
44
+ projectPath: string;
45
+ projectName?: string;
46
+ id: string;
47
+ title: string;
48
+ /** Session that owns the question: the assignee, else the team's lead */
49
+ asker: string;
50
+ /** Team whose channel carries the card */
51
+ teamId?: string;
52
+ }
53
+ /** A Slack place to post into. */
54
+ export interface DecisionSlackPlace {
55
+ slackChannelId: string;
56
+ threadTs?: string;
57
+ teamId?: string;
58
+ }
59
+ /** Collaborators. */
60
+ export interface DecisionServiceDeps {
61
+ store: DecisionStore;
62
+ threads: TicketThreadStore;
63
+ slack: () => DecisionSlackApi | null;
64
+ /** This instance (button values carry it; Cloud routes clicks by it) */
65
+ instanceId: () => string;
66
+ /** Whether a Slack user may answer (the owner) */
67
+ isOwner: (slackUserId: string) => boolean;
68
+ /** The owner's Slack user id, for mentions in reminders */
69
+ ownerUserId?: () => string | null;
70
+ /** Display name of a Slack user ("Steve") */
71
+ userName?: (slackUserId: string) => Promise<string | undefined>;
72
+ /** How an agent posts */
73
+ identityOf: (session: string) => Promise<DecisionPostIdentity>;
74
+ /** Slack channel of a team */
75
+ teamChannelOf: (teamId: string) => Promise<string | null>;
76
+ /** The team an agent belongs to */
77
+ teamOf: (session: string) => Promise<string | undefined>;
78
+ /** Resolve a ticket ask (access-checked); throws DecisionError */
79
+ resolveTicket: (project: string, ticketId: string, callerSession: string | undefined) => Promise<DecisionTicketContext>;
80
+ /** Mark the ticket as waiting on the owner (label + log line) */
81
+ markTicketAsked: (ctx: DecisionTicketContext, question: string, decisionId: string) => Promise<void>;
82
+ /** Append a ticket log line; `clearNeedsOwner` removes the needs-owner label */
83
+ logTicket: (ticket: NonNullable<OwnerDecision['ticket']>, line: string, clearNeedsOwner: boolean) => Promise<void>;
84
+ /** The Slack destination of the agent's current work, when it is a Slack place */
85
+ workDestination?: (session: string) => Promise<DecisionSlackPlace | null>;
86
+ /** The work item the agent is on */
87
+ currentWorkItemId?: (session: string) => Promise<string | undefined>;
88
+ /** Deliver a message to an agent (wakes a stopped one); false when it could not */
89
+ deliverToAgent: (session: string, text: string) => Promise<boolean>;
90
+ /** Close the owner-message watchdog entries this agent owes in the thread */
91
+ closeWatchdog?: (session: string, slackChannelId: string, threadTs: string) => void;
92
+ now?: () => Date;
93
+ logger?: ComponentLogger;
94
+ }
95
+ /** What a Slack interaction did. */
96
+ export interface InteractionOutcome {
97
+ handled: boolean;
98
+ reason: string;
99
+ decision?: OwnerDecision;
100
+ }
101
+ /** A Slack `block_actions` payload (the fields used). */
102
+ export interface BlockActionsPayload {
103
+ type?: string;
104
+ user?: {
105
+ id?: string;
106
+ name?: string;
107
+ username?: string;
108
+ };
109
+ actions?: Array<{
110
+ action_id?: string;
111
+ value?: string;
112
+ action_ts?: string;
113
+ }>;
114
+ container?: {
115
+ channel_id?: string;
116
+ message_ts?: string;
117
+ thread_ts?: string;
118
+ };
119
+ channel?: {
120
+ id?: string;
121
+ };
122
+ message?: {
123
+ ts?: string;
124
+ thread_ts?: string;
125
+ };
126
+ }
127
+ /** A `reaction_added` event (the fields used). */
128
+ export interface ReactionEvent {
129
+ user?: string;
130
+ reaction?: string;
131
+ item?: {
132
+ type?: string;
133
+ channel?: string;
134
+ ts?: string;
135
+ };
136
+ }
137
+ /**
138
+ * Decision cards service.
139
+ */
140
+ export declare class DecisionService {
141
+ private static instance;
142
+ private readonly deps;
143
+ private readonly logger;
144
+ private readonly now;
145
+ private timer;
146
+ private ticking;
147
+ /**
148
+ * @param deps - Collaborators
149
+ */
150
+ constructor(deps: DecisionServiceDeps);
151
+ /** @returns The process-wide instance, or null before wiring */
152
+ static getInstance(): DecisionService | null;
153
+ /** @param service - Instance to install (null clears) */
154
+ static setInstance(service: DecisionService | null): void;
155
+ /** Start the deadline / reminder tick. */
156
+ start(): void;
157
+ /** Stop the tick. */
158
+ stop(): void;
159
+ /**
160
+ * Ask the owner. Validates the contract, stores the decision and posts the
161
+ * card as the responsible agent's own bot.
162
+ *
163
+ * @param callerSession - Agent calling ask-owner (undefined = owner / dashboard)
164
+ * @param input - The ask
165
+ * @returns The stored decision (with `card`, or `postError` when Slack refused; retried on the tick)
166
+ * @throws DecisionError(400) for a contract violation, other 4xx from ticket resolution
167
+ */
168
+ ask(callerSession: string | undefined, input: AskOwnerInput): Promise<OwnerDecision>;
169
+ /**
170
+ * Post (or re-try posting) the card of an open decision.
171
+ *
172
+ * @param decision - Decision without a card
173
+ * @returns The decision with `card` or `postError`
174
+ */
175
+ postCard(decision: OwnerDecision): Promise<OwnerDecision>;
176
+ /**
177
+ * Where the card goes: the ticket's thread (created on first use), else the
178
+ * asker's current work destination, else a new thread in its team channel.
179
+ */
180
+ private placeFor;
181
+ /**
182
+ * Send as the agent's own bot; when Slack refuses that bot (not in the
183
+ * channel, revoked), fall back to the shared bot with the agent's name.
184
+ */
185
+ private send;
186
+ /**
187
+ * A Slack `block_actions` payload (Cloud relay, Socket Mode or the HTTP
188
+ * endpoint). Ignores anything that is not one of this instance's cards.
189
+ *
190
+ * @param payload - Slack payload
191
+ * @returns What happened
192
+ */
193
+ handleInteraction(payload: BlockActionsPayload): Promise<InteractionOutcome>;
194
+ /**
195
+ * A reaction on a card: ✅ default/first, ❌ the "no" option, ⏰ remind.
196
+ *
197
+ * @param event - `reaction_added` event
198
+ * @returns What happened
199
+ */
200
+ handleReaction(event: ReactionEvent): Promise<InteractionOutcome>;
201
+ /**
202
+ * An owner reply in a card's thread. The newest open card in that thread
203
+ * takes it; words that match no option are passed to the asker verbatim.
204
+ *
205
+ * @param message - Inbound Slack message
206
+ * @returns What happened
207
+ */
208
+ handleThreadReply(message: Pick<SlackIncomingMessage, 'channelId' | 'threadTs' | 'ts' | 'text' | 'userId' | 'authorAgentSession'>): Promise<InteractionOutcome>;
209
+ /**
210
+ * The owner answered from the dashboard.
211
+ *
212
+ * @param id - Decision id
213
+ * @param optionKey - Option key (or label / number)
214
+ * @returns The resolved decision
215
+ * @throws DecisionError(404/409/400)
216
+ */
217
+ chooseFromDashboard(id: string, optionKey: string): Promise<OwnerDecision>;
218
+ /**
219
+ * "Remind me tomorrow" from the dashboard.
220
+ *
221
+ * @param id - Decision id
222
+ * @returns The snoozed decision
223
+ */
224
+ remindFromDashboard(id: string): Promise<OwnerDecision>;
225
+ /**
226
+ * Withdraw open decisions (the asker no longer needs an answer, or the
227
+ * ticket's mark was cleared).
228
+ *
229
+ * @param filter - Which decisions
230
+ * @param note - Why (ticket log)
231
+ * @returns How many were withdrawn
232
+ */
233
+ cancelWhere(filter: (d: OwnerDecision) => boolean, note?: string): Promise<number>;
234
+ /**
235
+ * List decisions.
236
+ *
237
+ * @param which - `open` (open + parked) or `all`
238
+ * @returns Decisions, newest first
239
+ */
240
+ list(which?: 'open' | 'all'): Promise<OwnerDecision[]>;
241
+ /**
242
+ * One decision.
243
+ *
244
+ * @param id - Decision id
245
+ * @returns Decision or null
246
+ */
247
+ get(id: string): Promise<OwnerDecision | null>;
248
+ private requirePending;
249
+ /**
250
+ * Apply a choice: resolve (or snooze), update the card, log the ticket,
251
+ * tell the asker, close the watchdog entry.
252
+ */
253
+ private apply;
254
+ /** The note the asker receives when the owner answered. */
255
+ private answerNote;
256
+ /** Where the asker's follow-up belongs. */
257
+ private whereLine;
258
+ /**
259
+ * One pass: retry unposted cards, post due reminders, apply defaults at
260
+ * the deadline, re-ask / park sensitive asks, prune old decisions.
261
+ *
262
+ * @returns Ids acted on
263
+ */
264
+ tick(): Promise<string[]>;
265
+ /** Post the "Remind me tomorrow" reminder in the card's thread. */
266
+ private remindNow;
267
+ /** Non-sensitive deadline: apply the default (or say it keeps waiting). */
268
+ private applyDefault;
269
+ /** Sensitive deadline: re-ask once after 24 h, then park. Never auto-applied. */
270
+ private sensitiveStep;
271
+ /** Re-render the card for the decision's current state (with the posting bot's token). */
272
+ private refreshCard;
273
+ /** Post a line in the card's thread, as the asker. */
274
+ private postInThread;
275
+ private logTicket;
276
+ private tellAsker;
277
+ private closeWatchdog;
278
+ }
279
+ /** Re-exported for the deadline line in the dashboard/API. */
280
+ export { formatWhen };
281
+ //# sourceMappingURL=decision.service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decision.service.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/decisions/decision.service.ts"],"names":[],"mappings":"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"}