crewly 1.20.181 → 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 (136) hide show
  1. package/dist/backend/backend/src/constants.d.ts +66 -1
  2. package/dist/backend/backend/src/constants.d.ts.map +1 -1
  3. package/dist/backend/backend/src/constants.js +75 -1
  4. package/dist/backend/backend/src/constants.js.map +1 -1
  5. package/dist/backend/backend/src/controllers/system/runtime-fallback.controller.d.ts +11 -1
  6. package/dist/backend/backend/src/controllers/system/runtime-fallback.controller.d.ts.map +1 -1
  7. package/dist/backend/backend/src/controllers/system/runtime-fallback.controller.js +44 -9
  8. package/dist/backend/backend/src/controllers/system/runtime-fallback.controller.js.map +1 -1
  9. package/dist/backend/backend/src/controllers/system/runtime-terms.controller.d.ts +31 -0
  10. package/dist/backend/backend/src/controllers/system/runtime-terms.controller.d.ts.map +1 -0
  11. package/dist/backend/backend/src/controllers/system/runtime-terms.controller.js +99 -0
  12. package/dist/backend/backend/src/controllers/system/runtime-terms.controller.js.map +1 -0
  13. package/dist/backend/backend/src/index.d.ts.map +1 -1
  14. package/dist/backend/backend/src/index.js +4 -0
  15. package/dist/backend/backend/src/index.js.map +1 -1
  16. package/dist/backend/backend/src/routes/modules/system.routes.d.ts.map +1 -1
  17. package/dist/backend/backend/src/routes/modules/system.routes.js +3 -0
  18. package/dist/backend/backend/src/routes/modules/system.routes.js.map +1 -1
  19. package/dist/backend/backend/src/services/agent/antigravity-runtime.service.d.ts.map +1 -1
  20. package/dist/backend/backend/src/services/agent/antigravity-runtime.service.js +5 -0
  21. package/dist/backend/backend/src/services/agent/antigravity-runtime.service.js.map +1 -1
  22. package/dist/backend/backend/src/services/decisions/decision-card.d.ts +14 -4
  23. package/dist/backend/backend/src/services/decisions/decision-card.d.ts.map +1 -1
  24. package/dist/backend/backend/src/services/decisions/decision-card.js +31 -10
  25. package/dist/backend/backend/src/services/decisions/decision-card.js.map +1 -1
  26. package/dist/backend/backend/src/services/decisions/decision.service.d.ts +40 -2
  27. package/dist/backend/backend/src/services/decisions/decision.service.d.ts.map +1 -1
  28. package/dist/backend/backend/src/services/decisions/decision.service.js +100 -8
  29. package/dist/backend/backend/src/services/decisions/decision.service.js.map +1 -1
  30. package/dist/backend/backend/src/services/decisions/decision.wiring.d.ts.map +1 -1
  31. package/dist/backend/backend/src/services/decisions/decision.wiring.js +7 -0
  32. package/dist/backend/backend/src/services/decisions/decision.wiring.js.map +1 -1
  33. package/dist/backend/backend/src/services/runtime-fallback/runtime-availability.d.ts +8 -0
  34. package/dist/backend/backend/src/services/runtime-fallback/runtime-availability.d.ts.map +1 -1
  35. package/dist/backend/backend/src/services/runtime-fallback/runtime-availability.js +5 -0
  36. package/dist/backend/backend/src/services/runtime-fallback/runtime-availability.js.map +1 -1
  37. package/dist/backend/backend/src/services/runtime-fallback/runtime-fallback.service.d.ts +2 -0
  38. package/dist/backend/backend/src/services/runtime-fallback/runtime-fallback.service.d.ts.map +1 -1
  39. package/dist/backend/backend/src/services/runtime-fallback/runtime-fallback.service.js +4 -0
  40. package/dist/backend/backend/src/services/runtime-fallback/runtime-fallback.service.js.map +1 -1
  41. package/dist/backend/backend/src/services/runtime-fallback/runtime-fallback.types.d.ts +2 -0
  42. package/dist/backend/backend/src/services/runtime-fallback/runtime-fallback.types.d.ts.map +1 -1
  43. package/dist/backend/backend/src/services/runtime-fallback/runtime-fallback.types.js.map +1 -1
  44. package/dist/backend/backend/src/services/runtime-fallback/runtime-fallback.wiring.d.ts.map +1 -1
  45. package/dist/backend/backend/src/services/runtime-fallback/runtime-fallback.wiring.js +3 -0
  46. package/dist/backend/backend/src/services/runtime-fallback/runtime-fallback.wiring.js.map +1 -1
  47. package/dist/backend/backend/src/services/runtime-fallback/runtime-smoke-test.service.d.ts +15 -1
  48. package/dist/backend/backend/src/services/runtime-fallback/runtime-smoke-test.service.d.ts.map +1 -1
  49. package/dist/backend/backend/src/services/runtime-fallback/runtime-smoke-test.service.js +13 -2
  50. package/dist/backend/backend/src/services/runtime-fallback/runtime-smoke-test.service.js.map +1 -1
  51. package/dist/backend/backend/src/services/runtime-terms/antigravity-terms-driver.d.ts +92 -0
  52. package/dist/backend/backend/src/services/runtime-terms/antigravity-terms-driver.d.ts.map +1 -0
  53. package/dist/backend/backend/src/services/runtime-terms/antigravity-terms-driver.js +199 -0
  54. package/dist/backend/backend/src/services/runtime-terms/antigravity-terms-driver.js.map +1 -0
  55. package/dist/backend/backend/src/services/runtime-terms/antigravity-terms-screens.d.ts +79 -0
  56. package/dist/backend/backend/src/services/runtime-terms/antigravity-terms-screens.d.ts.map +1 -0
  57. package/dist/backend/backend/src/services/runtime-terms/antigravity-terms-screens.js +145 -0
  58. package/dist/backend/backend/src/services/runtime-terms/antigravity-terms-screens.js.map +1 -0
  59. package/dist/backend/backend/src/services/runtime-terms/fake-agy-tui.fixture.d.ts +75 -0
  60. package/dist/backend/backend/src/services/runtime-terms/fake-agy-tui.fixture.d.ts.map +1 -0
  61. package/dist/backend/backend/src/services/runtime-terms/fake-agy-tui.fixture.js +218 -0
  62. package/dist/backend/backend/src/services/runtime-terms/fake-agy-tui.fixture.js.map +1 -0
  63. package/dist/backend/backend/src/services/runtime-terms/runtime-terms-consent.service.d.ts +245 -0
  64. package/dist/backend/backend/src/services/runtime-terms/runtime-terms-consent.service.d.ts.map +1 -0
  65. package/dist/backend/backend/src/services/runtime-terms/runtime-terms-consent.service.js +428 -0
  66. package/dist/backend/backend/src/services/runtime-terms/runtime-terms-consent.service.js.map +1 -0
  67. package/dist/backend/backend/src/services/runtime-terms/runtime-terms.store.d.ts +67 -0
  68. package/dist/backend/backend/src/services/runtime-terms/runtime-terms.store.d.ts.map +1 -0
  69. package/dist/backend/backend/src/services/runtime-terms/runtime-terms.store.js +78 -0
  70. package/dist/backend/backend/src/services/runtime-terms/runtime-terms.store.js.map +1 -0
  71. package/dist/backend/backend/src/services/runtime-terms/runtime-terms.wiring.d.ts +39 -0
  72. package/dist/backend/backend/src/services/runtime-terms/runtime-terms.wiring.d.ts.map +1 -0
  73. package/dist/backend/backend/src/services/runtime-terms/runtime-terms.wiring.js +161 -0
  74. package/dist/backend/backend/src/services/runtime-terms/runtime-terms.wiring.js.map +1 -0
  75. package/dist/backend/backend/src/types/decision.types.d.ts +29 -5
  76. package/dist/backend/backend/src/types/decision.types.d.ts.map +1 -1
  77. package/dist/backend/build-info.json +2 -2
  78. package/dist/cli/backend/src/constants.d.ts +66 -1
  79. package/dist/cli/backend/src/constants.d.ts.map +1 -1
  80. package/dist/cli/backend/src/constants.js +75 -1
  81. package/dist/cli/backend/src/constants.js.map +1 -1
  82. package/dist/cli/backend/src/services/agent/antigravity-runtime.service.d.ts.map +1 -1
  83. package/dist/cli/backend/src/services/agent/antigravity-runtime.service.js +5 -0
  84. package/dist/cli/backend/src/services/agent/antigravity-runtime.service.js.map +1 -1
  85. package/dist/cli/backend/src/services/decisions/decision-card.d.ts +165 -0
  86. package/dist/cli/backend/src/services/decisions/decision-card.d.ts.map +1 -0
  87. package/dist/cli/backend/src/services/decisions/decision-card.js +358 -0
  88. package/dist/cli/backend/src/services/decisions/decision-card.js.map +1 -0
  89. package/dist/cli/backend/src/services/decisions/decision-contract.d.ts +83 -0
  90. package/dist/cli/backend/src/services/decisions/decision-contract.d.ts.map +1 -0
  91. package/dist/cli/backend/src/services/decisions/decision-contract.js +226 -0
  92. package/dist/cli/backend/src/services/decisions/decision-contract.js.map +1 -0
  93. package/dist/cli/backend/src/services/decisions/decision-store.d.ts +80 -0
  94. package/dist/cli/backend/src/services/decisions/decision-store.d.ts.map +1 -0
  95. package/dist/cli/backend/src/services/decisions/decision-store.js +151 -0
  96. package/dist/cli/backend/src/services/decisions/decision-store.js.map +1 -0
  97. package/dist/cli/backend/src/services/decisions/decision.service.d.ts +376 -0
  98. package/dist/cli/backend/src/services/decisions/decision.service.d.ts.map +1 -0
  99. package/dist/cli/backend/src/services/decisions/decision.service.js +821 -0
  100. package/dist/cli/backend/src/services/decisions/decision.service.js.map +1 -0
  101. package/dist/cli/backend/src/services/decisions/ticket-thread-store.d.ts +97 -0
  102. package/dist/cli/backend/src/services/decisions/ticket-thread-store.d.ts.map +1 -0
  103. package/dist/cli/backend/src/services/decisions/ticket-thread-store.js +137 -0
  104. package/dist/cli/backend/src/services/decisions/ticket-thread-store.js.map +1 -0
  105. package/dist/cli/backend/src/services/runtime-fallback/runtime-fallback.types.d.ts +136 -0
  106. package/dist/cli/backend/src/services/runtime-fallback/runtime-fallback.types.d.ts.map +1 -0
  107. package/dist/cli/backend/src/services/runtime-fallback/runtime-fallback.types.js +152 -0
  108. package/dist/cli/backend/src/services/runtime-fallback/runtime-fallback.types.js.map +1 -0
  109. package/dist/cli/backend/src/services/runtime-fallback/runtime-smoke-test.service.d.ts +217 -0
  110. package/dist/cli/backend/src/services/runtime-fallback/runtime-smoke-test.service.d.ts.map +1 -0
  111. package/dist/cli/backend/src/services/runtime-fallback/runtime-smoke-test.service.js +430 -0
  112. package/dist/cli/backend/src/services/runtime-fallback/runtime-smoke-test.service.js.map +1 -0
  113. package/dist/cli/backend/src/services/runtime-terms/antigravity-terms-driver.d.ts +92 -0
  114. package/dist/cli/backend/src/services/runtime-terms/antigravity-terms-driver.d.ts.map +1 -0
  115. package/dist/cli/backend/src/services/runtime-terms/antigravity-terms-driver.js +199 -0
  116. package/dist/cli/backend/src/services/runtime-terms/antigravity-terms-driver.js.map +1 -0
  117. package/dist/cli/backend/src/services/runtime-terms/antigravity-terms-screens.d.ts +79 -0
  118. package/dist/cli/backend/src/services/runtime-terms/antigravity-terms-screens.d.ts.map +1 -0
  119. package/dist/cli/backend/src/services/runtime-terms/antigravity-terms-screens.js +145 -0
  120. package/dist/cli/backend/src/services/runtime-terms/antigravity-terms-screens.js.map +1 -0
  121. package/dist/cli/backend/src/services/runtime-terms/runtime-terms-consent.service.d.ts +245 -0
  122. package/dist/cli/backend/src/services/runtime-terms/runtime-terms-consent.service.d.ts.map +1 -0
  123. package/dist/cli/backend/src/services/runtime-terms/runtime-terms-consent.service.js +428 -0
  124. package/dist/cli/backend/src/services/runtime-terms/runtime-terms-consent.service.js.map +1 -0
  125. package/dist/cli/backend/src/services/runtime-terms/runtime-terms.store.d.ts +67 -0
  126. package/dist/cli/backend/src/services/runtime-terms/runtime-terms.store.d.ts.map +1 -0
  127. package/dist/cli/backend/src/services/runtime-terms/runtime-terms.store.js +78 -0
  128. package/dist/cli/backend/src/services/runtime-terms/runtime-terms.store.js.map +1 -0
  129. package/dist/cli/backend/src/types/decision.types.d.ts +192 -0
  130. package/dist/cli/backend/src/types/decision.types.d.ts.map +1 -0
  131. package/dist/cli/backend/src/types/decision.types.js +13 -0
  132. package/dist/cli/backend/src/types/decision.types.js.map +1 -0
  133. package/dist/cli/cli/src/commands/harness-setup.js +2 -2
  134. package/frontend/dist/assets/{index-4271e95e.js → index-9c91de50.js} +138 -138
  135. package/frontend/dist/index.html +1 -1
  136. package/package.json +1 -1
@@ -0,0 +1,245 @@
1
+ /**
2
+ * Owner consent for a runtime's first-run Terms of Service.
3
+ *
4
+ * The harness never accepts third-party terms on its own. When a runtime's
5
+ * Terms screen is seen (on launch, by the smoke test, or on a probe), ONE
6
+ * decision card per runtime per machine goes to the owner's DM with this
7
+ * machine's orc bot: "Agree, no data sharing" / "Agree + share data" /
8
+ * "Don't agree", 24 h, default Don't agree. On the answer:
9
+ *
10
+ * - agree: launch the runtime in a dedicated PTY session, drive its setup
11
+ * screens to the choice (data box verified on screen before Done), check
12
+ * the prompt appears, close the session, run the runtime smoke test and
13
+ * report both in the card's thread;
14
+ * - don't agree (or no answer): mark the runtime "terms not accepted" so the
15
+ * fallback order skips it (reason shown in Settings → Runtimes); asked
16
+ * again only when the owner presses Test, re-adds it, or Accept terms….
17
+ *
18
+ * specs/2026-10-01-runtime-terms-consent.md
19
+ *
20
+ * @module services/runtime-terms/runtime-terms-consent.service
21
+ */
22
+ import type { OwnerDecision } from '../../types/decision.types.js';
23
+ import { type ComponentLogger } from '../core/logger.service.js';
24
+ import type { SystemAskInput } from '../decisions/decision.service.js';
25
+ import type { SmokeTestResult } from '../runtime-fallback/runtime-smoke-test.service.js';
26
+ import type { TermsDriveResult, TermsTerminal } from './antigravity-terms-driver.js';
27
+ import type { RuntimeTermsRecord, RuntimeTermsStore } from './runtime-terms.store.js';
28
+ /** The owner's three answers. */
29
+ export type TermsChoice = 'agree_no_data' | 'agree_share_data' | 'decline';
30
+ /** Card text of a runtime. */
31
+ export interface TermsCardText {
32
+ title: string;
33
+ question: string;
34
+ body: string[];
35
+ }
36
+ /** What Settings shows next to the inline choices. */
37
+ export interface TermsInfo {
38
+ /** One plain-English sentence on what is asked */
39
+ summary: string;
40
+ /** The pre-checked data-sharing item, verbatim */
41
+ dataItem: string;
42
+ links: Array<{
43
+ label: string;
44
+ url: string;
45
+ }>;
46
+ }
47
+ /** What the service knows about one runtime's Terms screens. */
48
+ export interface RuntimeTermsProfile {
49
+ runtime: string;
50
+ label: string;
51
+ info: TermsInfo;
52
+ /** Card text for this machine */
53
+ card(machine: string): TermsCardText;
54
+ /** Drive the setup screens of a freshly launched runtime */
55
+ drive(term: TermsTerminal, shareData: boolean): Promise<TermsDriveResult>;
56
+ /** What a screen shows: the Terms / setup screens, the ready prompt, a refusal, or not yet known */
57
+ classify(screen: string): 'terms' | 'ready' | 'blocked' | 'unknown';
58
+ }
59
+ /** Result of {@link RuntimeTermsConsentService.probe}. */
60
+ export interface TermsProbeResult {
61
+ outcome: 'terms' | 'ready' | 'blocked' | 'unknown';
62
+ record: RuntimeTermsRecord | null;
63
+ /** Screen text at the end (secrets redacted) */
64
+ screen: string;
65
+ }
66
+ /** A launched dedicated session. */
67
+ export interface TermsSession {
68
+ terminal: TermsTerminal;
69
+ /** Leave the runtime and kill the session (always called) */
70
+ close(): Promise<void>;
71
+ }
72
+ /** The decision-card calls used. */
73
+ export interface TermsDecisionApi {
74
+ askSystem(input: SystemAskInput): Promise<OwnerDecision>;
75
+ get(id: string): Promise<OwnerDecision | null>;
76
+ chooseFromDashboard(id: string, optionKey: string): Promise<OwnerDecision>;
77
+ replyInThread(id: string, text: string): Promise<boolean>;
78
+ }
79
+ /** Dependencies. */
80
+ export interface RuntimeTermsDeps {
81
+ store: RuntimeTermsStore;
82
+ decisions: () => TermsDecisionApi | null;
83
+ profiles: Record<string, RuntimeTermsProfile>;
84
+ /** Launch the runtime in the harness's dedicated session */
85
+ launch: (runtime: string) => Promise<TermsSession>;
86
+ /** Kill the dedicated session if it is running (Don't agree) */
87
+ closeSession?: (runtime: string) => Promise<void>;
88
+ runSmokeTest: (runtime: string) => Promise<SmokeTestResult>;
89
+ machineName: () => string;
90
+ /** A runtime's consent changed (availability caches) */
91
+ onChange?: (runtime: string) => void;
92
+ now?: () => Date;
93
+ sleep?: (ms: number) => Promise<void>;
94
+ /** How long a probe waits for a recognisable screen */
95
+ probeTimeoutMs?: number;
96
+ logger?: ComponentLogger;
97
+ }
98
+ /** How a report was triggered. */
99
+ export interface TermsReport {
100
+ /** launch | smoke_test | probe | settings | chain */
101
+ source: string;
102
+ /** The owner asked (Test, Accept terms…, re-added): re-ask even after Don't agree */
103
+ ownerInitiated?: boolean;
104
+ }
105
+ /** A runtime's Terms state as the API shows it. */
106
+ export interface RuntimeTermsView extends Omit<RuntimeTermsRecord, 'status' | 'updatedAt'> {
107
+ /** `none` = never seen a Terms screen on this machine */
108
+ status: RuntimeTermsRecord['status'] | 'none';
109
+ updatedAt?: string;
110
+ label: string;
111
+ info: TermsInfo;
112
+ /** Why the runtime is skipped, when it is */
113
+ blockedReason: string | null;
114
+ /** Choices the inline Settings action offers */
115
+ choices: Array<{
116
+ choice: TermsChoice;
117
+ label: string;
118
+ }>;
119
+ }
120
+ /** Choice ↔ button label. */
121
+ export declare const TERMS_CHOICE_LABELS: Readonly<Record<TermsChoice, string>>;
122
+ /**
123
+ * The choice an option label means.
124
+ *
125
+ * @param label - Button label
126
+ * @returns Choice, or null
127
+ */
128
+ export declare function choiceOfLabel(label: string | undefined): TermsChoice | null;
129
+ /**
130
+ * Screen text for a Slack code block: secrets redacted, last lines only, no
131
+ * fence breakers.
132
+ *
133
+ * @param screen - Screen text
134
+ * @returns Block text
135
+ */
136
+ export declare function screenForThread(screen: string): string;
137
+ /** The service. */
138
+ export declare class RuntimeTermsConsentService {
139
+ private readonly deps;
140
+ private readonly logger;
141
+ private readonly now;
142
+ /** Serialises asks per runtime (launch + smoke test can report at once) */
143
+ private readonly asking;
144
+ /** Running accept / decline per runtime */
145
+ private readonly running;
146
+ /**
147
+ * @param deps - Dependencies
148
+ */
149
+ constructor(deps: RuntimeTermsDeps);
150
+ /**
151
+ * Whether the service handles a runtime.
152
+ *
153
+ * @param runtime - Runtime id
154
+ * @returns True when it has a profile
155
+ */
156
+ supports(runtime: string): boolean;
157
+ /**
158
+ * A runtime's Terms screen was seen: ask the owner, once.
159
+ *
160
+ * No new card while one is open or the harness is accepting; none after
161
+ * Don't agree / a failed attempt unless the owner asked
162
+ * (`ownerInitiated`).
163
+ *
164
+ * @param runtime - Runtime id
165
+ * @param report - Source
166
+ * @returns The record, or null for a runtime without a profile
167
+ */
168
+ reportTermsScreen(runtime: string, report: TermsReport): Promise<RuntimeTermsRecord | null>;
169
+ /**
170
+ * Settings → Runtimes → Accept terms…: post the card (or return the open one).
171
+ *
172
+ * @param runtime - Runtime id
173
+ * @returns The record
174
+ * @throws Error for a runtime without Terms handling
175
+ */
176
+ requestConsent(runtime: string): Promise<RuntimeTermsRecord>;
177
+ /**
178
+ * Probe: launch the runtime in the dedicated session, read (never press
179
+ * anything) until the screen is recognisable, close it. A Terms screen
180
+ * asks the owner (the owner asked for the check); a ready prompt means
181
+ * the Terms were accepted here already (e.g. in a terminal).
182
+ *
183
+ * @param runtime - Runtime id
184
+ * @returns What was on screen and the record after
185
+ * @throws Error while an answer is being applied, or for a runtime without a profile
186
+ */
187
+ probe(runtime: string): Promise<TermsProbeResult>;
188
+ /**
189
+ * Answer inline from Settings: through the open card when there is one
190
+ * (so the card updates), else directly.
191
+ *
192
+ * @param runtime - Runtime id
193
+ * @param choice - Owner's choice
194
+ * @returns The record after the answer was taken (driving continues in the background)
195
+ */
196
+ answer(runtime: string, choice: TermsChoice): Promise<RuntimeTermsRecord>;
197
+ /**
198
+ * The `runtime_terms` decision-kind handler
199
+ * (`DecisionService.registerKindHandler`): acts on the answer. No agent
200
+ * asked, so there is no note for one.
201
+ *
202
+ * @param d - Settled decision
203
+ * @returns null (nothing to deliver)
204
+ */
205
+ onSettled(d: OwnerDecision): Promise<null>;
206
+ /**
207
+ * A decision was settled: act on runtime-terms ones.
208
+ *
209
+ * @param d - Settled decision
210
+ */
211
+ handleSettled(d: OwnerDecision): void;
212
+ /**
213
+ * Why a runtime cannot be used because of its Terms (null = it can).
214
+ *
215
+ * @param runtime - Runtime id
216
+ * @returns Owner-facing reason, or null
217
+ */
218
+ blockedReason(runtime: string): string | null;
219
+ /** @returns Every runtime with a Terms flow, with its record */
220
+ list(): RuntimeTermsView[];
221
+ /**
222
+ * Wait for a running accept / decline (tests, the API's `?wait=1`).
223
+ *
224
+ * @param runtime - Runtime id
225
+ */
226
+ whenIdle(runtime: string): Promise<void>;
227
+ private askOnce;
228
+ private start;
229
+ private decline;
230
+ private accept;
231
+ private reply;
232
+ private save;
233
+ }
234
+ /** @returns The backend's instance, or null before wiring (and in tests) */
235
+ export declare function getRuntimeTermsConsentService(): RuntimeTermsConsentService | null;
236
+ /** @param service - Instance to install (null clears) */
237
+ export declare function setRuntimeTermsConsentService(service: RuntimeTermsConsentService | null): void;
238
+ /**
239
+ * Report a Terms screen to the backend's service, when wired (fire and forget).
240
+ *
241
+ * @param runtime - Runtime id
242
+ * @param report - Source
243
+ */
244
+ export declare function reportRuntimeTermsScreen(runtime: string, report: TermsReport): void;
245
+ //# sourceMappingURL=runtime-terms-consent.service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime-terms-consent.service.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/runtime-terms/runtime-terms-consent.service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,428 @@
1
+ /**
2
+ * Owner consent for a runtime's first-run Terms of Service.
3
+ *
4
+ * The harness never accepts third-party terms on its own. When a runtime's
5
+ * Terms screen is seen (on launch, by the smoke test, or on a probe), ONE
6
+ * decision card per runtime per machine goes to the owner's DM with this
7
+ * machine's orc bot: "Agree, no data sharing" / "Agree + share data" /
8
+ * "Don't agree", 24 h, default Don't agree. On the answer:
9
+ *
10
+ * - agree: launch the runtime in a dedicated PTY session, drive its setup
11
+ * screens to the choice (data box verified on screen before Done), check
12
+ * the prompt appears, close the session, run the runtime smoke test and
13
+ * report both in the card's thread;
14
+ * - don't agree (or no answer): mark the runtime "terms not accepted" so the
15
+ * fallback order skips it (reason shown in Settings → Runtimes); asked
16
+ * again only when the owner presses Test, re-adds it, or Accept terms….
17
+ *
18
+ * specs/2026-10-01-runtime-terms-consent.md
19
+ *
20
+ * @module services/runtime-terms/runtime-terms-consent.service
21
+ */
22
+ import { RUNTIME_TERMS_CONSTANTS } from '../../constants.js';
23
+ import { LoggerService } from '../core/logger.service.js';
24
+ import { PENDING_DECISION_STATUSES } from '../decisions/decision-store.js';
25
+ import { redactSecrets } from '../harness/login-rules.js';
26
+ const C = RUNTIME_TERMS_CONSTANTS;
27
+ /** Choice ↔ button label. */
28
+ export const TERMS_CHOICE_LABELS = {
29
+ agree_no_data: C.OPTIONS.AGREE_NO_DATA,
30
+ agree_share_data: C.OPTIONS.AGREE_SHARE_DATA,
31
+ decline: C.OPTIONS.DECLINE,
32
+ };
33
+ /**
34
+ * The choice an option label means.
35
+ *
36
+ * @param label - Button label
37
+ * @returns Choice, or null
38
+ */
39
+ export function choiceOfLabel(label) {
40
+ const entry = Object.entries(TERMS_CHOICE_LABELS).find(([, l]) => l === label);
41
+ return entry ? entry[0] : null;
42
+ }
43
+ /**
44
+ * Screen text for a Slack code block: secrets redacted, last lines only, no
45
+ * fence breakers.
46
+ *
47
+ * @param screen - Screen text
48
+ * @returns Block text
49
+ */
50
+ export function screenForThread(screen) {
51
+ const lines = redactSecrets(screen)
52
+ .split('\n')
53
+ .map((l) => l.replace(/\s+$/, ''));
54
+ while (lines.length && !lines[lines.length - 1])
55
+ lines.pop();
56
+ const tail = lines.slice(-C.DRIVE.SCREEN_LINES_IN_THREAD).join('\n').replace(/```/g, "'''");
57
+ return '```\n' + (tail || '(empty screen)') + '\n```';
58
+ }
59
+ /** The service. */
60
+ export class RuntimeTermsConsentService {
61
+ deps;
62
+ logger;
63
+ now;
64
+ /** Serialises asks per runtime (launch + smoke test can report at once) */
65
+ asking = new Map();
66
+ /** Running accept / decline per runtime */
67
+ running = new Map();
68
+ /**
69
+ * @param deps - Dependencies
70
+ */
71
+ constructor(deps) {
72
+ this.deps = deps;
73
+ this.logger = deps.logger ?? LoggerService.getInstance().createComponentLogger('RuntimeTerms');
74
+ this.now = deps.now ?? (() => new Date());
75
+ }
76
+ /**
77
+ * Whether the service handles a runtime.
78
+ *
79
+ * @param runtime - Runtime id
80
+ * @returns True when it has a profile
81
+ */
82
+ supports(runtime) {
83
+ return Boolean(this.deps.profiles[runtime]);
84
+ }
85
+ /**
86
+ * A runtime's Terms screen was seen: ask the owner, once.
87
+ *
88
+ * No new card while one is open or the harness is accepting; none after
89
+ * Don't agree / a failed attempt unless the owner asked
90
+ * (`ownerInitiated`).
91
+ *
92
+ * @param runtime - Runtime id
93
+ * @param report - Source
94
+ * @returns The record, or null for a runtime without a profile
95
+ */
96
+ reportTermsScreen(runtime, report) {
97
+ if (!this.supports(runtime))
98
+ return Promise.resolve(null);
99
+ const prev = this.asking.get(runtime) ?? Promise.resolve(null);
100
+ const next = prev.catch(() => null).then(() => this.askOnce(runtime, report));
101
+ this.asking.set(runtime, next);
102
+ return next;
103
+ }
104
+ /**
105
+ * Settings → Runtimes → Accept terms…: post the card (or return the open one).
106
+ *
107
+ * @param runtime - Runtime id
108
+ * @returns The record
109
+ * @throws Error for a runtime without Terms handling
110
+ */
111
+ async requestConsent(runtime) {
112
+ if (!this.supports(runtime))
113
+ throw new Error(`Crewly has no Terms flow for ${runtime}`);
114
+ return (await this.reportTermsScreen(runtime, { source: 'settings', ownerInitiated: true }));
115
+ }
116
+ /**
117
+ * Probe: launch the runtime in the dedicated session, read (never press
118
+ * anything) until the screen is recognisable, close it. A Terms screen
119
+ * asks the owner (the owner asked for the check); a ready prompt means
120
+ * the Terms were accepted here already (e.g. in a terminal).
121
+ *
122
+ * @param runtime - Runtime id
123
+ * @returns What was on screen and the record after
124
+ * @throws Error while an answer is being applied, or for a runtime without a profile
125
+ */
126
+ async probe(runtime) {
127
+ const profile = this.deps.profiles[runtime];
128
+ if (!profile)
129
+ throw new Error(`Crewly has no Terms flow for ${runtime}`);
130
+ if (this.running.has(runtime) || this.deps.store.get(runtime)?.status === 'accepting') {
131
+ throw new Error('Crewly is already accepting these Terms; wait for it to finish');
132
+ }
133
+ const sleep = this.deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
134
+ const timeout = this.deps.probeTimeoutMs ?? C.DRIVE.LAUNCH_TIMEOUT_MS;
135
+ let screen = '';
136
+ let outcome = 'unknown';
137
+ const session = await this.deps.launch(runtime);
138
+ try {
139
+ for (let waited = 0; waited <= timeout; waited += C.DRIVE.POLL_MS) {
140
+ screen = String(await session.terminal.capture());
141
+ outcome = profile.classify(screen);
142
+ if (outcome !== 'unknown')
143
+ break;
144
+ await sleep(C.DRIVE.POLL_MS);
145
+ }
146
+ }
147
+ finally {
148
+ await session.close().catch(() => undefined);
149
+ }
150
+ if (outcome === 'terms') {
151
+ const record = await this.reportTermsScreen(runtime, { source: 'probe', ownerInitiated: true });
152
+ return { outcome, record, screen: redactSecrets(screen) };
153
+ }
154
+ if (outcome === 'ready') {
155
+ const prev = this.deps.store.get(runtime);
156
+ const record = this.save({ runtime, status: 'accepted', detectedBy: 'probe', ...(prev?.dataSharing !== undefined ? { dataSharing: prev.dataSharing } : {}) });
157
+ return { outcome, record, screen: redactSecrets(screen) };
158
+ }
159
+ return { outcome, record: this.deps.store.get(runtime), screen: redactSecrets(screen) };
160
+ }
161
+ /**
162
+ * Answer inline from Settings: through the open card when there is one
163
+ * (so the card updates), else directly.
164
+ *
165
+ * @param runtime - Runtime id
166
+ * @param choice - Owner's choice
167
+ * @returns The record after the answer was taken (driving continues in the background)
168
+ */
169
+ async answer(runtime, choice) {
170
+ if (!this.supports(runtime))
171
+ throw new Error(`Crewly has no Terms flow for ${runtime}`);
172
+ const rec = this.deps.store.get(runtime);
173
+ if (rec?.status === 'accepting')
174
+ throw new Error('Crewly is already accepting these Terms; wait for it to finish');
175
+ const decisions = this.deps.decisions();
176
+ if (rec?.decisionId && decisions) {
177
+ const d = await decisions.get(rec.decisionId);
178
+ const opt = d?.options.find((o) => o.label === TERMS_CHOICE_LABELS[choice]);
179
+ if (d && opt && PENDING_DECISION_STATUSES.has(d.status)) {
180
+ await decisions.chooseFromDashboard(d.id, opt.key);
181
+ return this.deps.store.get(runtime) ?? rec;
182
+ }
183
+ }
184
+ this.start(runtime, choice, undefined, 'owner');
185
+ return this.deps.store.get(runtime) ?? rec;
186
+ }
187
+ /**
188
+ * The `runtime_terms` decision-kind handler
189
+ * (`DecisionService.registerKindHandler`): acts on the answer. No agent
190
+ * asked, so there is no note for one.
191
+ *
192
+ * @param d - Settled decision
193
+ * @returns null (nothing to deliver)
194
+ */
195
+ async onSettled(d) {
196
+ this.handleSettled(d);
197
+ return null;
198
+ }
199
+ /**
200
+ * A decision was settled: act on runtime-terms ones.
201
+ *
202
+ * @param d - Settled decision
203
+ */
204
+ handleSettled(d) {
205
+ if (d.kind !== 'runtime_terms' || !d.system)
206
+ return;
207
+ const runtime = d.system.key;
208
+ const rec = this.deps.store.get(runtime);
209
+ if (!rec || rec.decisionId !== d.id) {
210
+ this.logger.info('Ignoring an old Terms decision', { decisionId: d.id, runtime });
211
+ return;
212
+ }
213
+ if (d.status === 'resolved') {
214
+ const choice = choiceOfLabel(d.options.find((o) => o.key === d.chosenKey)?.label);
215
+ if (choice)
216
+ this.start(runtime, choice, d.id, 'owner');
217
+ return;
218
+ }
219
+ if (d.status === 'defaulted') {
220
+ this.start(runtime, 'decline', d.id, 'deadline');
221
+ return;
222
+ }
223
+ // Withdrawn / parked: nothing was agreed.
224
+ this.start(runtime, 'decline', d.id, 'withdrawn');
225
+ }
226
+ /**
227
+ * Why a runtime cannot be used because of its Terms (null = it can).
228
+ *
229
+ * @param runtime - Runtime id
230
+ * @returns Owner-facing reason, or null
231
+ */
232
+ blockedReason(runtime) {
233
+ const rec = this.deps.store.get(runtime);
234
+ if (!rec)
235
+ return null;
236
+ switch (rec.status) {
237
+ case 'pending':
238
+ return C.MESSAGES.PENDING;
239
+ case 'accepting':
240
+ return 'Accepting its Terms of Service…';
241
+ case 'declined':
242
+ return `${C.MESSAGES.DECLINED_SUFFIX}: ${rec.reason ?? C.MESSAGES.DECLINED_BY_OWNER}`;
243
+ case 'failed':
244
+ return `${C.MESSAGES.DECLINED_SUFFIX}: the setup stopped (${rec.reason ?? 'unknown reason'})`;
245
+ default:
246
+ return null;
247
+ }
248
+ }
249
+ /** @returns Every runtime with a Terms flow, with its record */
250
+ list() {
251
+ const all = this.deps.store.all();
252
+ return Object.values(this.deps.profiles).map((p) => ({
253
+ ...(all[p.runtime] ?? { runtime: p.runtime, status: 'none' }),
254
+ label: p.label,
255
+ info: p.info,
256
+ blockedReason: this.blockedReason(p.runtime),
257
+ choices: Object.keys(TERMS_CHOICE_LABELS).map((choice) => ({ choice, label: TERMS_CHOICE_LABELS[choice] })),
258
+ }));
259
+ }
260
+ /**
261
+ * Wait for a running accept / decline (tests, the API's `?wait=1`).
262
+ *
263
+ * @param runtime - Runtime id
264
+ */
265
+ async whenIdle(runtime) {
266
+ await (this.asking.get(runtime) ?? Promise.resolve()).catch(() => undefined);
267
+ await (this.running.get(runtime) ?? Promise.resolve());
268
+ }
269
+ // ------------------------------------------------------------------ internals
270
+ async askOnce(runtime, report) {
271
+ const profile = this.deps.profiles[runtime];
272
+ const rec = this.deps.store.get(runtime);
273
+ if (rec?.status === 'accepting')
274
+ return rec;
275
+ const decisions = this.deps.decisions();
276
+ if (rec?.status === 'pending' && rec.decisionId && decisions) {
277
+ const open = await decisions.get(rec.decisionId).catch(() => null);
278
+ if (open && PENDING_DECISION_STATUSES.has(open.status))
279
+ return rec;
280
+ }
281
+ if ((rec?.status === 'declined' || rec?.status === 'failed') && !report.ownerInitiated)
282
+ return rec;
283
+ const machine = this.deps.machineName();
284
+ let decisionId;
285
+ if (decisions) {
286
+ const text = profile.card(machine);
287
+ try {
288
+ const d = await decisions.askSystem({
289
+ kind: 'runtime_terms',
290
+ system: { key: runtime, defaultIsDecline: true },
291
+ title: text.title,
292
+ question: text.question,
293
+ body: text.body,
294
+ options: [C.OPTIONS.AGREE_NO_DATA, C.OPTIONS.AGREE_SHARE_DATA, C.OPTIONS.DECLINE],
295
+ default: C.OPTIONS.DECLINE,
296
+ deadline: new Date(this.now().getTime() + C.DEADLINE_MS),
297
+ sensitive: 'runtime_terms',
298
+ });
299
+ decisionId = d.id;
300
+ }
301
+ catch (err) {
302
+ this.logger.warn('Could not create the Terms decision card', { runtime, error: err instanceof Error ? err.message : String(err) });
303
+ }
304
+ }
305
+ const saved = this.save({
306
+ runtime,
307
+ status: 'pending',
308
+ ...(decisionId ? { decisionId } : {}),
309
+ detectedBy: report.source,
310
+ });
311
+ this.logger.info('Runtime Terms consent asked', { runtime, source: report.source, decisionId });
312
+ return saved;
313
+ }
314
+ start(runtime, choice, decisionId, why) {
315
+ if (this.running.has(runtime)) {
316
+ this.logger.warn('A Terms answer is already being applied; ignoring another', { runtime, choice });
317
+ return;
318
+ }
319
+ const run = (choice === 'decline' ? this.decline(runtime, decisionId, why) : this.accept(runtime, choice === 'agree_share_data', decisionId))
320
+ .catch((err) => this.logger.error('Applying the Terms answer failed', { runtime, error: err instanceof Error ? err.message : String(err) }))
321
+ .finally(() => this.running.delete(runtime));
322
+ this.running.set(runtime, run);
323
+ }
324
+ async decline(runtime, decisionId, why) {
325
+ const profile = this.deps.profiles[runtime];
326
+ await this.deps.closeSession?.(runtime).catch(() => undefined);
327
+ const reason = why === 'deadline' ? C.MESSAGES.DECLINED_BY_DEADLINE : why === 'withdrawn' ? 'The question was withdrawn without an answer' : C.MESSAGES.DECLINED_BY_OWNER;
328
+ this.save({ runtime, status: 'declined', reason, ...(decisionId ? { decisionId } : {}) });
329
+ const machine = this.deps.machineName();
330
+ const lead = why === 'deadline' ? 'No answer within 24 h, so nothing was accepted (the default).' : 'OK, not accepted.';
331
+ await this.reply(decisionId, `${lead} ${profile.label} is marked "terms not accepted" on ${machine}, and the fallback order skips it. ` +
332
+ `To change your mind: Settings → Runtimes → ${profile.label} → Accept terms…, or press Test.`);
333
+ }
334
+ async accept(runtime, shareData, decisionId) {
335
+ const profile = this.deps.profiles[runtime];
336
+ const machine = this.deps.machineName();
337
+ this.save({ runtime, status: 'accepting', ...(decisionId ? { decisionId } : {}), dataSharing: shareData });
338
+ let drive;
339
+ let session = null;
340
+ try {
341
+ session = await this.deps.launch(runtime);
342
+ drive = await profile.drive(session.terminal, shareData);
343
+ }
344
+ catch (err) {
345
+ drive = { ok: false, donePressed: false, alreadyAccepted: false, error: `Could not start ${profile.label}: ${err instanceof Error ? err.message : String(err)}`, keys: [], screen: '' };
346
+ }
347
+ finally {
348
+ await session?.close().catch(() => undefined);
349
+ }
350
+ this.logger.info('Runtime Terms screens driven', { runtime, ok: drive.ok, donePressed: drive.donePressed, alreadyAccepted: drive.alreadyAccepted, keys: drive.keys.length });
351
+ if (!drive.ok) {
352
+ this.save({
353
+ runtime,
354
+ status: 'failed',
355
+ reason: drive.error ?? 'unknown',
356
+ ...(decisionId ? { decisionId } : {}),
357
+ ...(drive.donePressed ? { dataSharing: drive.dataSharing } : {}),
358
+ });
359
+ const head = drive.donePressed
360
+ ? `:warning: Done was pressed on ${machine} (Terms accepted, data sharing ${drive.dataSharing ? 'on' : 'off'}), but then: ${drive.error}.`
361
+ : `:warning: Stopped on ${machine}; nothing was accepted (Done was not pressed): ${drive.error}.`;
362
+ await this.reply(decisionId, `${head}\nScreen:\n${screenForThread(drive.screen)}\nTo try again: Settings → Runtimes → ${profile.label} → Accept terms….`);
363
+ return;
364
+ }
365
+ this.save({
366
+ runtime,
367
+ status: 'accepted',
368
+ ...(decisionId ? { decisionId } : {}),
369
+ ...(drive.alreadyAccepted ? {} : { dataSharing: drive.dataSharing ?? shareData }),
370
+ });
371
+ await this.reply(decisionId, drive.alreadyAccepted
372
+ ? `${profile.label} on ${machine} was already set up (it opened straight to its prompt), so nothing was pressed. Running the runtime test…`
373
+ : `:white_check_mark: Accepted on ${machine}: ${profile.label}'s Terms, data sharing ${drive.dataSharing ? 'on' : 'off'} (checked on screen before Done). It opened to its prompt. Running the runtime test…`);
374
+ let smoke;
375
+ try {
376
+ smoke = await this.deps.runSmokeTest(runtime);
377
+ }
378
+ catch (err) {
379
+ await this.reply(decisionId, `Runtime test could not start: ${err instanceof Error ? err.message : String(err)}`);
380
+ return;
381
+ }
382
+ await this.reply(decisionId, smoke.passed
383
+ ? `Runtime test passed in ${Math.round(smoke.durationMs / 1000)}s: ${profile.label} ran bash and replied.`
384
+ : `Runtime test failed at "${(smoke.failedStep ?? 'unknown').replace(/_/g, ' ')}": ${smoke.error ?? 'no reason given'}` +
385
+ (smoke.screen ? `\nScreen:\n${screenForThread(smoke.screen)}` : ''));
386
+ }
387
+ async reply(decisionId, text) {
388
+ if (!decisionId)
389
+ return;
390
+ const decisions = this.deps.decisions();
391
+ if (!decisions)
392
+ return;
393
+ await decisions.replyInThread(decisionId, text).catch((err) => this.logger.warn('Could not reply in the Terms card thread', { decisionId, error: String(err) }));
394
+ }
395
+ save(fields) {
396
+ const saved = this.deps.store.set({ ...fields, updatedAt: this.now().toISOString() });
397
+ try {
398
+ this.deps.onChange?.(fields.runtime);
399
+ }
400
+ catch {
401
+ // cache invalidation only
402
+ }
403
+ return saved;
404
+ }
405
+ }
406
+ // ----------------------------------------------------------------- singleton
407
+ let instance = null;
408
+ /** @returns The backend's instance, or null before wiring (and in tests) */
409
+ export function getRuntimeTermsConsentService() {
410
+ return instance;
411
+ }
412
+ /** @param service - Instance to install (null clears) */
413
+ export function setRuntimeTermsConsentService(service) {
414
+ instance = service;
415
+ }
416
+ /**
417
+ * Report a Terms screen to the backend's service, when wired (fire and forget).
418
+ *
419
+ * @param runtime - Runtime id
420
+ * @param report - Source
421
+ */
422
+ export function reportRuntimeTermsScreen(runtime, report) {
423
+ const svc = instance;
424
+ if (!svc)
425
+ return;
426
+ void svc.reportTermsScreen(runtime, report).catch(() => undefined);
427
+ }
428
+ //# sourceMappingURL=runtime-terms-consent.service.js.map