crewly 1.20.180 → 1.20.182

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 (157) hide show
  1. package/config/skills/agent/remote-browser/SKILL.md +28 -8
  2. package/dist/backend/backend/src/constants.d.ts +89 -1
  3. package/dist/backend/backend/src/constants.d.ts.map +1 -1
  4. package/dist/backend/backend/src/constants.js +99 -2
  5. package/dist/backend/backend/src/constants.js.map +1 -1
  6. package/dist/backend/backend/src/controllers/browser/browser.controller.d.ts +2 -1
  7. package/dist/backend/backend/src/controllers/browser/browser.controller.d.ts.map +1 -1
  8. package/dist/backend/backend/src/controllers/browser/browser.controller.js +15 -1
  9. package/dist/backend/backend/src/controllers/browser/browser.controller.js.map +1 -1
  10. package/dist/backend/backend/src/controllers/system/runtime-fallback.controller.d.ts +11 -1
  11. package/dist/backend/backend/src/controllers/system/runtime-fallback.controller.d.ts.map +1 -1
  12. package/dist/backend/backend/src/controllers/system/runtime-fallback.controller.js +44 -9
  13. package/dist/backend/backend/src/controllers/system/runtime-fallback.controller.js.map +1 -1
  14. package/dist/backend/backend/src/controllers/system/runtime-terms.controller.d.ts +31 -0
  15. package/dist/backend/backend/src/controllers/system/runtime-terms.controller.d.ts.map +1 -0
  16. package/dist/backend/backend/src/controllers/system/runtime-terms.controller.js +99 -0
  17. package/dist/backend/backend/src/controllers/system/runtime-terms.controller.js.map +1 -0
  18. package/dist/backend/backend/src/index.d.ts.map +1 -1
  19. package/dist/backend/backend/src/index.js +51 -0
  20. package/dist/backend/backend/src/index.js.map +1 -1
  21. package/dist/backend/backend/src/routes/modules/system.routes.d.ts.map +1 -1
  22. package/dist/backend/backend/src/routes/modules/system.routes.js +3 -0
  23. package/dist/backend/backend/src/routes/modules/system.routes.js.map +1 -1
  24. package/dist/backend/backend/src/services/agent/antigravity-runtime.service.d.ts.map +1 -1
  25. package/dist/backend/backend/src/services/agent/antigravity-runtime.service.js +5 -0
  26. package/dist/backend/backend/src/services/agent/antigravity-runtime.service.js.map +1 -1
  27. package/dist/backend/backend/src/services/browser/browser-approval.service.d.ts +202 -0
  28. package/dist/backend/backend/src/services/browser/browser-approval.service.d.ts.map +1 -0
  29. package/dist/backend/backend/src/services/browser/browser-approval.service.js +533 -0
  30. package/dist/backend/backend/src/services/browser/browser-approval.service.js.map +1 -0
  31. package/dist/backend/backend/src/services/browser/browser-bridge.service.d.ts +24 -0
  32. package/dist/backend/backend/src/services/browser/browser-bridge.service.d.ts.map +1 -1
  33. package/dist/backend/backend/src/services/browser/browser-bridge.service.js +55 -0
  34. package/dist/backend/backend/src/services/browser/browser-bridge.service.js.map +1 -1
  35. package/dist/backend/backend/src/services/browser/browser-session.service.d.ts +51 -0
  36. package/dist/backend/backend/src/services/browser/browser-session.service.d.ts.map +1 -1
  37. package/dist/backend/backend/src/services/browser/browser-session.service.js +82 -3
  38. package/dist/backend/backend/src/services/browser/browser-session.service.js.map +1 -1
  39. package/dist/backend/backend/src/services/browser/held-action-store.d.ts +125 -0
  40. package/dist/backend/backend/src/services/browser/held-action-store.d.ts.map +1 -0
  41. package/dist/backend/backend/src/services/browser/held-action-store.js +141 -0
  42. package/dist/backend/backend/src/services/browser/held-action-store.js.map +1 -0
  43. package/dist/backend/backend/src/services/decisions/decision-card.d.ts +23 -5
  44. package/dist/backend/backend/src/services/decisions/decision-card.d.ts.map +1 -1
  45. package/dist/backend/backend/src/services/decisions/decision-card.js +56 -9
  46. package/dist/backend/backend/src/services/decisions/decision-card.js.map +1 -1
  47. package/dist/backend/backend/src/services/decisions/decision.service.d.ts +96 -1
  48. package/dist/backend/backend/src/services/decisions/decision.service.d.ts.map +1 -1
  49. package/dist/backend/backend/src/services/decisions/decision.service.js +190 -8
  50. package/dist/backend/backend/src/services/decisions/decision.service.js.map +1 -1
  51. package/dist/backend/backend/src/services/decisions/decision.wiring.d.ts.map +1 -1
  52. package/dist/backend/backend/src/services/decisions/decision.wiring.js +7 -0
  53. package/dist/backend/backend/src/services/decisions/decision.wiring.js.map +1 -1
  54. package/dist/backend/backend/src/services/runtime-fallback/runtime-availability.d.ts +8 -0
  55. package/dist/backend/backend/src/services/runtime-fallback/runtime-availability.d.ts.map +1 -1
  56. package/dist/backend/backend/src/services/runtime-fallback/runtime-availability.js +5 -0
  57. package/dist/backend/backend/src/services/runtime-fallback/runtime-availability.js.map +1 -1
  58. package/dist/backend/backend/src/services/runtime-fallback/runtime-fallback.service.d.ts +2 -0
  59. package/dist/backend/backend/src/services/runtime-fallback/runtime-fallback.service.d.ts.map +1 -1
  60. package/dist/backend/backend/src/services/runtime-fallback/runtime-fallback.service.js +4 -0
  61. package/dist/backend/backend/src/services/runtime-fallback/runtime-fallback.service.js.map +1 -1
  62. package/dist/backend/backend/src/services/runtime-fallback/runtime-fallback.types.d.ts +2 -0
  63. package/dist/backend/backend/src/services/runtime-fallback/runtime-fallback.types.d.ts.map +1 -1
  64. package/dist/backend/backend/src/services/runtime-fallback/runtime-fallback.types.js.map +1 -1
  65. package/dist/backend/backend/src/services/runtime-fallback/runtime-fallback.wiring.d.ts.map +1 -1
  66. package/dist/backend/backend/src/services/runtime-fallback/runtime-fallback.wiring.js +3 -0
  67. package/dist/backend/backend/src/services/runtime-fallback/runtime-fallback.wiring.js.map +1 -1
  68. package/dist/backend/backend/src/services/runtime-fallback/runtime-smoke-test.service.d.ts +15 -1
  69. package/dist/backend/backend/src/services/runtime-fallback/runtime-smoke-test.service.d.ts.map +1 -1
  70. package/dist/backend/backend/src/services/runtime-fallback/runtime-smoke-test.service.js +13 -2
  71. package/dist/backend/backend/src/services/runtime-fallback/runtime-smoke-test.service.js.map +1 -1
  72. package/dist/backend/backend/src/services/runtime-terms/antigravity-terms-driver.d.ts +92 -0
  73. package/dist/backend/backend/src/services/runtime-terms/antigravity-terms-driver.d.ts.map +1 -0
  74. package/dist/backend/backend/src/services/runtime-terms/antigravity-terms-driver.js +199 -0
  75. package/dist/backend/backend/src/services/runtime-terms/antigravity-terms-driver.js.map +1 -0
  76. package/dist/backend/backend/src/services/runtime-terms/antigravity-terms-screens.d.ts +79 -0
  77. package/dist/backend/backend/src/services/runtime-terms/antigravity-terms-screens.d.ts.map +1 -0
  78. package/dist/backend/backend/src/services/runtime-terms/antigravity-terms-screens.js +145 -0
  79. package/dist/backend/backend/src/services/runtime-terms/antigravity-terms-screens.js.map +1 -0
  80. package/dist/backend/backend/src/services/runtime-terms/fake-agy-tui.fixture.d.ts +75 -0
  81. package/dist/backend/backend/src/services/runtime-terms/fake-agy-tui.fixture.d.ts.map +1 -0
  82. package/dist/backend/backend/src/services/runtime-terms/fake-agy-tui.fixture.js +218 -0
  83. package/dist/backend/backend/src/services/runtime-terms/fake-agy-tui.fixture.js.map +1 -0
  84. package/dist/backend/backend/src/services/runtime-terms/runtime-terms-consent.service.d.ts +245 -0
  85. package/dist/backend/backend/src/services/runtime-terms/runtime-terms-consent.service.d.ts.map +1 -0
  86. package/dist/backend/backend/src/services/runtime-terms/runtime-terms-consent.service.js +428 -0
  87. package/dist/backend/backend/src/services/runtime-terms/runtime-terms-consent.service.js.map +1 -0
  88. package/dist/backend/backend/src/services/runtime-terms/runtime-terms.store.d.ts +67 -0
  89. package/dist/backend/backend/src/services/runtime-terms/runtime-terms.store.d.ts.map +1 -0
  90. package/dist/backend/backend/src/services/runtime-terms/runtime-terms.store.js +78 -0
  91. package/dist/backend/backend/src/services/runtime-terms/runtime-terms.store.js.map +1 -0
  92. package/dist/backend/backend/src/services/runtime-terms/runtime-terms.wiring.d.ts +39 -0
  93. package/dist/backend/backend/src/services/runtime-terms/runtime-terms.wiring.d.ts.map +1 -0
  94. package/dist/backend/backend/src/services/runtime-terms/runtime-terms.wiring.js +161 -0
  95. package/dist/backend/backend/src/services/runtime-terms/runtime-terms.wiring.js.map +1 -0
  96. package/dist/backend/backend/src/types/decision.types.d.ts +55 -2
  97. package/dist/backend/backend/src/types/decision.types.d.ts.map +1 -1
  98. package/dist/backend/build-info.json +2 -2
  99. package/dist/cli/backend/src/constants.d.ts +89 -1
  100. package/dist/cli/backend/src/constants.d.ts.map +1 -1
  101. package/dist/cli/backend/src/constants.js +99 -2
  102. package/dist/cli/backend/src/constants.js.map +1 -1
  103. package/dist/cli/backend/src/services/agent/antigravity-runtime.service.d.ts.map +1 -1
  104. package/dist/cli/backend/src/services/agent/antigravity-runtime.service.js +5 -0
  105. package/dist/cli/backend/src/services/agent/antigravity-runtime.service.js.map +1 -1
  106. package/dist/cli/backend/src/services/decisions/decision-card.d.ts +165 -0
  107. package/dist/cli/backend/src/services/decisions/decision-card.d.ts.map +1 -0
  108. package/dist/cli/backend/src/services/decisions/decision-card.js +358 -0
  109. package/dist/cli/backend/src/services/decisions/decision-card.js.map +1 -0
  110. package/dist/cli/backend/src/services/decisions/decision-contract.d.ts +83 -0
  111. package/dist/cli/backend/src/services/decisions/decision-contract.d.ts.map +1 -0
  112. package/dist/cli/backend/src/services/decisions/decision-contract.js +226 -0
  113. package/dist/cli/backend/src/services/decisions/decision-contract.js.map +1 -0
  114. package/dist/cli/backend/src/services/decisions/decision-store.d.ts +80 -0
  115. package/dist/cli/backend/src/services/decisions/decision-store.d.ts.map +1 -0
  116. package/dist/cli/backend/src/services/decisions/decision-store.js +151 -0
  117. package/dist/cli/backend/src/services/decisions/decision-store.js.map +1 -0
  118. package/dist/cli/backend/src/services/decisions/decision.service.d.ts +376 -0
  119. package/dist/cli/backend/src/services/decisions/decision.service.d.ts.map +1 -0
  120. package/dist/cli/backend/src/services/decisions/decision.service.js +821 -0
  121. package/dist/cli/backend/src/services/decisions/decision.service.js.map +1 -0
  122. package/dist/cli/backend/src/services/decisions/ticket-thread-store.d.ts +97 -0
  123. package/dist/cli/backend/src/services/decisions/ticket-thread-store.d.ts.map +1 -0
  124. package/dist/cli/backend/src/services/decisions/ticket-thread-store.js +137 -0
  125. package/dist/cli/backend/src/services/decisions/ticket-thread-store.js.map +1 -0
  126. package/dist/cli/backend/src/services/runtime-fallback/runtime-fallback.types.d.ts +136 -0
  127. package/dist/cli/backend/src/services/runtime-fallback/runtime-fallback.types.d.ts.map +1 -0
  128. package/dist/cli/backend/src/services/runtime-fallback/runtime-fallback.types.js +152 -0
  129. package/dist/cli/backend/src/services/runtime-fallback/runtime-fallback.types.js.map +1 -0
  130. package/dist/cli/backend/src/services/runtime-fallback/runtime-smoke-test.service.d.ts +217 -0
  131. package/dist/cli/backend/src/services/runtime-fallback/runtime-smoke-test.service.d.ts.map +1 -0
  132. package/dist/cli/backend/src/services/runtime-fallback/runtime-smoke-test.service.js +430 -0
  133. package/dist/cli/backend/src/services/runtime-fallback/runtime-smoke-test.service.js.map +1 -0
  134. package/dist/cli/backend/src/services/runtime-terms/antigravity-terms-driver.d.ts +92 -0
  135. package/dist/cli/backend/src/services/runtime-terms/antigravity-terms-driver.d.ts.map +1 -0
  136. package/dist/cli/backend/src/services/runtime-terms/antigravity-terms-driver.js +199 -0
  137. package/dist/cli/backend/src/services/runtime-terms/antigravity-terms-driver.js.map +1 -0
  138. package/dist/cli/backend/src/services/runtime-terms/antigravity-terms-screens.d.ts +79 -0
  139. package/dist/cli/backend/src/services/runtime-terms/antigravity-terms-screens.d.ts.map +1 -0
  140. package/dist/cli/backend/src/services/runtime-terms/antigravity-terms-screens.js +145 -0
  141. package/dist/cli/backend/src/services/runtime-terms/antigravity-terms-screens.js.map +1 -0
  142. package/dist/cli/backend/src/services/runtime-terms/runtime-terms-consent.service.d.ts +245 -0
  143. package/dist/cli/backend/src/services/runtime-terms/runtime-terms-consent.service.d.ts.map +1 -0
  144. package/dist/cli/backend/src/services/runtime-terms/runtime-terms-consent.service.js +428 -0
  145. package/dist/cli/backend/src/services/runtime-terms/runtime-terms-consent.service.js.map +1 -0
  146. package/dist/cli/backend/src/services/runtime-terms/runtime-terms.store.d.ts +67 -0
  147. package/dist/cli/backend/src/services/runtime-terms/runtime-terms.store.d.ts.map +1 -0
  148. package/dist/cli/backend/src/services/runtime-terms/runtime-terms.store.js +78 -0
  149. package/dist/cli/backend/src/services/runtime-terms/runtime-terms.store.js.map +1 -0
  150. package/dist/cli/backend/src/types/decision.types.d.ts +192 -0
  151. package/dist/cli/backend/src/types/decision.types.d.ts.map +1 -0
  152. package/dist/cli/backend/src/types/decision.types.js +13 -0
  153. package/dist/cli/backend/src/types/decision.types.js.map +1 -0
  154. package/dist/cli/cli/src/commands/harness-setup.js +2 -2
  155. package/frontend/dist/assets/{index-4271e95e.js → index-9c91de50.js} +138 -138
  156. package/frontend/dist/index.html +1 -1
  157. package/package.json +1 -1
@@ -0,0 +1,226 @@
1
+ /**
2
+ * The ask-owner contract (specs/2026-10-01-decision-cards.md §1): validate a
3
+ * structured owner question and turn it into a {@link ValidatedAsk}. Pure.
4
+ *
5
+ * Every rejection says what to fix and shows a correct ask, because the
6
+ * reader is an agent that will retry with whatever the error tells it.
7
+ *
8
+ * @module services/decisions/decision-contract
9
+ */
10
+ import { DECISION_CONSTANTS } from '../../constants.js';
11
+ /** A rejected ask. */
12
+ export class DecisionContractError extends Error {
13
+ constructor(message) {
14
+ super(message);
15
+ this.name = 'DecisionContractError';
16
+ }
17
+ }
18
+ /** The example appended to every contract error. */
19
+ export const ASK_OWNER_EXAMPLE = 'Example: ask-owner --question "Send the partner email on Monday?" --option "Send Monday — after the review call" --option "Hold — wait for legal" --default "Hold" [--deadline 2026-10-02T12:00] [--ticket APP-12 --project P] [--sensitive email]';
20
+ const OPTION_KEYS = ['a', 'b', 'c'];
21
+ /** Separators accepted between an option's label and its detail in a string form. */
22
+ const DETAIL_SEPARATOR = /\s+(?:—|–|--|-|:)\s+/;
23
+ /**
24
+ * Collapse whitespace.
25
+ *
26
+ * @param s - Text
27
+ * @returns One trimmed line
28
+ */
29
+ function oneLine(s) {
30
+ return s.replace(/\s+/g, ' ').trim();
31
+ }
32
+ /**
33
+ * The next day at {@link DECISION_CONSTANTS.DEFAULT_DEADLINE_HOUR_LOCAL}:00 local.
34
+ *
35
+ * @param now - Clock
36
+ * @returns Default deadline
37
+ */
38
+ export function defaultDeadline(now) {
39
+ const d = new Date(now.getTime());
40
+ d.setDate(d.getDate() + 1);
41
+ d.setHours(DECISION_CONSTANTS.DEFAULT_DEADLINE_HOUR_LOCAL, 0, 0, 0);
42
+ return d;
43
+ }
44
+ /**
45
+ * Whether a question is too vague to put in front of the owner.
46
+ *
47
+ * @param question - One-line question
48
+ * @returns True for "thoughts?", "OK?", "可以吗" and the like
49
+ */
50
+ export function isVagueQuestion(question) {
51
+ const core = question.toLowerCase().replace(/[\s??!!.。…]+$/u, '').trim();
52
+ return DECISION_CONSTANTS.VAGUE_QUESTIONS.includes(core);
53
+ }
54
+ /**
55
+ * Parse the options: an array of `{label, detail?}` objects or of strings
56
+ * (`"Label"` / `"Label — detail"`).
57
+ *
58
+ * @param raw - Input
59
+ * @returns Options with keys a/b/c
60
+ * @throws DecisionContractError
61
+ */
62
+ export function parseOptions(raw) {
63
+ if (!Array.isArray(raw) || raw.length === 0) {
64
+ throw new DecisionContractError(`options are required: give the owner ${DECISION_CONSTANTS.MIN_OPTIONS}–${DECISION_CONSTANTS.MAX_OPTIONS} concrete choices they can tap. ${ASK_OWNER_EXAMPLE}`);
65
+ }
66
+ if (raw.length < DECISION_CONSTANTS.MIN_OPTIONS || raw.length > DECISION_CONSTANTS.MAX_OPTIONS) {
67
+ throw new DecisionContractError(`give ${DECISION_CONSTANTS.MIN_OPTIONS}–${DECISION_CONSTANTS.MAX_OPTIONS} options (got ${raw.length}). Pick the real alternatives; leave out "other". ${ASK_OWNER_EXAMPLE}`);
68
+ }
69
+ const options = raw.map((item, i) => {
70
+ let label = '';
71
+ let detail;
72
+ if (typeof item === 'string') {
73
+ const text = oneLine(item);
74
+ const m = DETAIL_SEPARATOR.exec(text);
75
+ if (m && m.index > 0) {
76
+ label = text.slice(0, m.index).trim();
77
+ detail = text.slice(m.index + m[0].length).trim() || undefined;
78
+ }
79
+ else {
80
+ label = text;
81
+ }
82
+ }
83
+ else if (item && typeof item === 'object') {
84
+ const o = item;
85
+ label = typeof o.label === 'string' ? oneLine(o.label) : '';
86
+ detail = typeof o.detail === 'string' && oneLine(o.detail) ? oneLine(o.detail) : undefined;
87
+ }
88
+ if (!label)
89
+ throw new DecisionContractError(`option ${i + 1} has no label. ${ASK_OWNER_EXAMPLE}`);
90
+ if (label.length > DECISION_CONSTANTS.OPTION_LABEL_MAX_CHARS) {
91
+ throw new DecisionContractError(`option "${label.slice(0, 30)}…" is too long for a button (max ${DECISION_CONSTANTS.OPTION_LABEL_MAX_CHARS} characters): put the short choice first and the explanation after " — ".`);
92
+ }
93
+ if (detail && detail.length > DECISION_CONSTANTS.OPTION_DETAIL_MAX_CHARS) {
94
+ throw new DecisionContractError(`the detail of option "${label}" is too long (max ${DECISION_CONSTANTS.OPTION_DETAIL_MAX_CHARS} characters).`);
95
+ }
96
+ return { key: OPTION_KEYS[i], label, ...(detail ? { detail } : {}) };
97
+ });
98
+ const seen = new Set();
99
+ for (const o of options) {
100
+ const k = o.label.toLowerCase();
101
+ if (seen.has(k))
102
+ throw new DecisionContractError(`two options are both "${o.label}": each option must be a different choice.`);
103
+ seen.add(k);
104
+ }
105
+ return options;
106
+ }
107
+ /**
108
+ * Resolve the default to an option key or `wait`.
109
+ *
110
+ * @param raw - Label, key (a/b/c), number (1–3) or `wait`
111
+ * @param options - Parsed options
112
+ * @returns Option key or `wait`
113
+ * @throws DecisionContractError
114
+ */
115
+ export function resolveDefault(raw, options) {
116
+ const text = typeof raw === 'string' ? oneLine(raw) : typeof raw === 'number' ? String(raw) : '';
117
+ if (!text) {
118
+ throw new DecisionContractError(`default is required: the option you will take if the owner does not answer by the deadline, or "wait" to do nothing until they answer. ${ASK_OWNER_EXAMPLE}`);
119
+ }
120
+ const found = matchOption(text, options);
121
+ if (found)
122
+ return found.key;
123
+ if (text.toLowerCase() === DECISION_CONSTANTS.WAIT_DEFAULT)
124
+ return DECISION_CONSTANTS.WAIT_DEFAULT;
125
+ throw new DecisionContractError(`default "${text}" is not one of the options (${options.map((o) => `"${o.label}"`).join(', ')}) and not "wait".`);
126
+ }
127
+ /**
128
+ * Find the option a label, key (a/b/c) or number (1–3) names.
129
+ *
130
+ * @param text - What was given
131
+ * @param options - Options
132
+ * @returns The option, or null
133
+ */
134
+ export function matchOption(text, options) {
135
+ const t = oneLine(text).toLowerCase().replace(/[.。!!]+$/u, '');
136
+ if (!t)
137
+ return null;
138
+ const byLabel = options.find((o) => o.label.toLowerCase() === t);
139
+ if (byLabel)
140
+ return byLabel;
141
+ const byKey = options.find((o) => o.key === t);
142
+ if (byKey)
143
+ return byKey;
144
+ const n = /^#?(\d)$/.exec(t);
145
+ if (n) {
146
+ const idx = Number(n[1]) - 1;
147
+ if (idx >= 0 && idx < options.length)
148
+ return options[idx];
149
+ }
150
+ return null;
151
+ }
152
+ /**
153
+ * Parse the deadline.
154
+ *
155
+ * @param raw - ISO / date-time string, or empty for the default
156
+ * @param now - Clock
157
+ * @returns The deadline
158
+ * @throws DecisionContractError when unreadable or not in the future
159
+ */
160
+ export function parseDeadline(raw, now) {
161
+ if (raw === undefined || raw === null || (typeof raw === 'string' && raw.trim() === ''))
162
+ return defaultDeadline(now);
163
+ const ms = typeof raw === 'number' ? raw : Date.parse(String(raw).trim());
164
+ if (!Number.isFinite(ms)) {
165
+ throw new DecisionContractError(`deadline "${String(raw)}" is not a date-time. Use ISO, e.g. 2026-10-02T12:00, or leave it out (default: tomorrow 12:00).`);
166
+ }
167
+ if (ms <= now.getTime())
168
+ throw new DecisionContractError(`deadline ${new Date(ms).toISOString()} is in the past.`);
169
+ return new Date(ms);
170
+ }
171
+ /**
172
+ * Parse the sensitive kind.
173
+ *
174
+ * @param raw - `email` | `publish` | `deploy` | `spend`, or empty
175
+ * @returns The kind, or undefined
176
+ * @throws DecisionContractError on anything else
177
+ */
178
+ export function parseSensitive(raw) {
179
+ if (raw === undefined || raw === null || raw === '' || raw === false)
180
+ return undefined;
181
+ const v = String(raw).trim().toLowerCase();
182
+ if (DECISION_CONSTANTS.SENSITIVE_KINDS.includes(v))
183
+ return v;
184
+ throw new DecisionContractError(`sensitive must be one of ${DECISION_CONSTANTS.SENSITIVE_KINDS.join(' | ')} (outside email/messages, public publishing, prod deploys, spending money).`);
185
+ }
186
+ /**
187
+ * Validate a whole ask.
188
+ *
189
+ * @param input - Raw input (skill / API body)
190
+ * @param now - Clock
191
+ * @returns The validated ask
192
+ * @throws DecisionContractError with a fix-it message
193
+ */
194
+ export function validateAskOwner(input, now = new Date()) {
195
+ const rawQ = typeof input.question === 'string' ? input.question : '';
196
+ if (/\n\s*\S/.test(rawQ.trim())) {
197
+ throw new DecisionContractError('question must be ONE line. Put the context in the ticket, the choices in --option. ' + ASK_OWNER_EXAMPLE);
198
+ }
199
+ const question = oneLine(rawQ);
200
+ if (!question)
201
+ throw new DecisionContractError(`question is required. ${ASK_OWNER_EXAMPLE}`);
202
+ if (question.length > DECISION_CONSTANTS.QUESTION_MAX_CHARS) {
203
+ throw new DecisionContractError(`question is too long (max ${DECISION_CONSTANTS.QUESTION_MAX_CHARS} characters) — one line the owner can answer with a tap.`);
204
+ }
205
+ if (question.length < DECISION_CONSTANTS.QUESTION_MIN_CHARS || isVagueQuestion(question)) {
206
+ throw new DecisionContractError(`"${question}" is too vague to answer from a phone. Name the concrete decision (what, and when), e.g. "Send the partner email on Monday?". ${ASK_OWNER_EXAMPLE}`);
207
+ }
208
+ const options = parseOptions(input.options);
209
+ const defaultKey = resolveDefault(input.default, options);
210
+ const deadline = parseDeadline(input.deadline, now);
211
+ const sensitive = parseSensitive(input.sensitive);
212
+ const ticketId = typeof input.ticket === 'string' && input.ticket.trim() ? input.ticket.trim() : undefined;
213
+ const project = typeof input.project === 'string' && input.project.trim() ? input.project.trim() : undefined;
214
+ if (ticketId && !project)
215
+ throw new DecisionContractError('ticket needs --project too (the project the ticket belongs to).');
216
+ return {
217
+ question,
218
+ options,
219
+ defaultKey,
220
+ deadline,
221
+ ...(sensitive ? { sensitive } : {}),
222
+ ...(ticketId ? { ticketId } : {}),
223
+ ...(project ? { project } : {}),
224
+ };
225
+ }
226
+ //# sourceMappingURL=decision-contract.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decision-contract.js","sourceRoot":"","sources":["../../../../../../backend/src/services/decisions/decision-contract.ts"],"names":[],"mappings":"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"}
@@ -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":"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"}