@buoy-gg/scenarios 7.0.76 → 7.0.77

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.
@@ -23,7 +23,26 @@
23
23
  */
24
24
  import { dispatchToolAction } from "@buoy-gg/shared-ui";
25
25
  import { type PreflightResult } from "../engine";
26
+ import { type TeamClientDeps, type TeamScenarioRow } from "../team/teamClient";
26
27
  import type { ActiveScenario, DeactivateReport, RunReport, Scenario, ScenarioVarValue, StepReport } from "../types";
28
+ /** Tests swap the buoy.gg calls. Null puts the real ones back. */
29
+ export declare function setTeamClientDepsForTests(deps: TeamClientDeps | null): void;
30
+ /** One row from buoy.gg as a read-only `team` scenario. */
31
+ export declare function teamScenarioFromRow(row: TeamScenarioRow): Scenario;
32
+ /**
33
+ * The team list's state. `off` means there is no team list here (no sign-in
34
+ * on a team, or no app id) and `message` says why. `error` keeps the last list.
35
+ */
36
+ export interface TeamInfo {
37
+ status: "idle" | "loading" | "ready" | "off" | "error";
38
+ message?: string;
39
+ /** Why it is `off`: no team sign-in, no app id, or buoy.gg said no. */
40
+ reason?: "no-team" | "no-app" | "refused";
41
+ /** The signed-in account, to tell which team scenarios are yours. */
42
+ you: string | null;
43
+ admin: boolean;
44
+ pulledAt?: number;
45
+ }
27
46
  /** Device library cap — a phone is not a database. */
28
47
  export declare const MAX_DEVICE_SCENARIOS = 100;
29
48
  export interface RunState {
@@ -42,6 +61,9 @@ export interface ScenariosState {
42
61
  device: Scenario[];
43
62
  /** Authored remotely, inert until accepted. */
44
63
  drafts: Scenario[];
64
+ /** Shared by this app's team, pulled from buoy.gg — read-only on device. */
65
+ team: Scenario[];
66
+ teamInfo: TeamInfo;
45
67
  active: ActiveScenario | null;
46
68
  runState: RunState | null;
47
69
  lastDeactivateReport: DeactivateReport | null;
@@ -61,7 +83,11 @@ declare class ScenariosStore {
61
83
  getState: () => ScenariosState;
62
84
  subscribe: (listener: () => void) => (() => void);
63
85
  private setState;
64
- /** All runnable scenarios, code first; a code scenario wins an id clash. */
86
+ /**
87
+ * All runnable scenarios: code, then the team's, then this device's. An id
88
+ * clash goes the same way, so a scenario shared from this device shows once,
89
+ * as the team's copy.
90
+ */
65
91
  allScenarios(): Scenario[];
66
92
  find(id: string): Scenario | undefined;
67
93
  /**
@@ -78,6 +104,38 @@ declare class ScenariosStore {
78
104
  private load;
79
105
  private persistLibrary;
80
106
  private persistActive;
107
+ private persistTeam;
108
+ private teamRows;
109
+ private teamPull;
110
+ /** The token and team the last pull went out with, to notice a new sign-in. */
111
+ private teamCredentialKey;
112
+ private credentialKey;
113
+ /**
114
+ * Account access changed (a sign-in, a sign-out, a new key). Pull again only
115
+ * when the team credential is a different one; other license events pass.
116
+ */
117
+ onAccountChange(): Promise<void>;
118
+ /**
119
+ * Pull the team's scenarios for this app from buoy.gg. Opening Scenarios
120
+ * calls this; a pull from the last 30 s counts unless `force` (Refresh).
121
+ * No team sign-in, or no app id: the team list goes away and says why.
122
+ * buoy.gg unreachable: the last list stays, with the reason.
123
+ */
124
+ refreshTeam(options?: {
125
+ force?: boolean;
126
+ }): Promise<void>;
127
+ private pullTeam;
128
+ /**
129
+ * Share a scenario saved on this device with the team. A person's tap only:
130
+ * no tool action, MCP call or Desktop panel reaches this.
131
+ */
132
+ shareWithTeam(id: string): Promise<Scenario>;
133
+ /** True in a dashboard that mirrors a device (Desktop). Sharing is the device's to do. */
134
+ isMirror(): boolean;
135
+ /** Whether this person may take a team scenario off the list: its sharer or an admin. */
136
+ canRemoveFromTeam(scenario: Scenario): boolean;
137
+ /** Take a team scenario off the team's list, for everyone. Sharer or admin, a person's tap only. */
138
+ removeFromTeam(id: string): Promise<void>;
81
139
  /**
82
140
  * Verify the active scenario's effects still exist. An override rule can be
83
141
  * deleted from the Network tool, cleared by the launch guard, or lost to a
@@ -28,6 +28,7 @@ export declare const scenariosSyncAdapter: {
28
28
  };
29
29
  createdAt: number;
30
30
  updatedAt: number;
31
+ team: import("../types").ScenarioTeamInfo | undefined;
31
32
  steps: (ScenarioStep | {
32
33
  params: undefined;
33
34
  paramsOmitted: boolean;
@@ -66,6 +67,7 @@ export declare const scenariosSyncAdapter: {
66
67
  };
67
68
  createdAt: number;
68
69
  updatedAt: number;
70
+ team: import("../types").ScenarioTeamInfo | undefined;
69
71
  steps: (ScenarioStep | {
70
72
  params: undefined;
71
73
  paramsOmitted: boolean;
@@ -104,6 +106,7 @@ export declare const scenariosSyncAdapter: {
104
106
  };
105
107
  createdAt: number;
106
108
  updatedAt: number;
109
+ team: import("../types").ScenarioTeamInfo | undefined;
107
110
  steps: (ScenarioStep | {
108
111
  params: undefined;
109
112
  paramsOmitted: boolean;
@@ -118,6 +121,46 @@ export declare const scenariosSyncAdapter: {
118
121
  durability: import("..").Durability;
119
122
  }[];
120
123
  }[];
124
+ team: {
125
+ id: string;
126
+ name: string;
127
+ description: string | undefined;
128
+ version: number;
129
+ source: import("..").ScenarioSource;
130
+ author: string | undefined;
131
+ folder: string | undefined;
132
+ tags: string[] | undefined;
133
+ vars: import("..").ScenarioVar[];
134
+ stepCount: number;
135
+ expectedOutcome: string | undefined;
136
+ authoredAgainst: string | undefined;
137
+ verifiedAt: {
138
+ build: string;
139
+ at: number;
140
+ } | undefined;
141
+ unsavedToRepo: boolean | undefined;
142
+ usage: {
143
+ runs: number;
144
+ lastRunAt?: number;
145
+ };
146
+ createdAt: number;
147
+ updatedAt: number;
148
+ team: import("../types").ScenarioTeamInfo | undefined;
149
+ steps: (ScenarioStep | {
150
+ params: undefined;
151
+ paramsOmitted: boolean;
152
+ id: string;
153
+ tool: string;
154
+ action: string;
155
+ label?: string;
156
+ timeoutMs?: number;
157
+ })[];
158
+ stepLines: {
159
+ label: string;
160
+ durability: import("..").Durability;
161
+ }[];
162
+ }[];
163
+ teamInfo: import("..").TeamInfo;
121
164
  active: import("..").ActiveScenario | null;
122
165
  runState: import("..").RunState | null;
123
166
  hasLoaded: boolean;
@@ -151,6 +194,7 @@ export declare const scenariosSyncAdapter: {
151
194
  };
152
195
  createdAt: number;
153
196
  updatedAt: number;
197
+ team: import("../types").ScenarioTeamInfo | undefined;
154
198
  steps: (ScenarioStep | {
155
199
  params: undefined;
156
200
  paramsOmitted: boolean;
@@ -189,6 +233,7 @@ export declare const scenariosSyncAdapter: {
189
233
  };
190
234
  createdAt: number;
191
235
  updatedAt: number;
236
+ team: import("../types").ScenarioTeamInfo | undefined;
192
237
  steps: (ScenarioStep | {
193
238
  params: undefined;
194
239
  paramsOmitted: boolean;
@@ -227,6 +272,46 @@ export declare const scenariosSyncAdapter: {
227
272
  };
228
273
  createdAt: number;
229
274
  updatedAt: number;
275
+ team: import("../types").ScenarioTeamInfo | undefined;
276
+ steps: (ScenarioStep | {
277
+ params: undefined;
278
+ paramsOmitted: boolean;
279
+ id: string;
280
+ tool: string;
281
+ action: string;
282
+ label?: string;
283
+ timeoutMs?: number;
284
+ })[];
285
+ stepLines: {
286
+ label: string;
287
+ durability: import("..").Durability;
288
+ }[];
289
+ }[];
290
+ team: {
291
+ id: string;
292
+ name: string;
293
+ description: string | undefined;
294
+ version: number;
295
+ source: import("..").ScenarioSource;
296
+ author: string | undefined;
297
+ folder: string | undefined;
298
+ tags: string[] | undefined;
299
+ vars: import("..").ScenarioVar[];
300
+ stepCount: number;
301
+ expectedOutcome: string | undefined;
302
+ authoredAgainst: string | undefined;
303
+ verifiedAt: {
304
+ build: string;
305
+ at: number;
306
+ } | undefined;
307
+ unsavedToRepo: boolean | undefined;
308
+ usage: {
309
+ runs: number;
310
+ lastRunAt?: number;
311
+ };
312
+ createdAt: number;
313
+ updatedAt: number;
314
+ team: import("../types").ScenarioTeamInfo | undefined;
230
315
  steps: (ScenarioStep | {
231
316
  params: undefined;
232
317
  paramsOmitted: boolean;
@@ -241,6 +326,7 @@ export declare const scenariosSyncAdapter: {
241
326
  durability: import("..").Durability;
242
327
  }[];
243
328
  }[];
329
+ teamInfo: import("..").TeamInfo;
244
330
  active: import("..").ActiveScenario | null;
245
331
  runState: import("..").RunState | null;
246
332
  hasLoaded: boolean;
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Shared Scenarios for teams: the three calls to buoy.gg (SHARED_SCENARIOS_PLAN.md).
3
+ *
4
+ * A device sends the token it already holds, a sign-in or the dev token from
5
+ * `buoy login`, plus this app's id. buoy.gg keeps one row per team, app and
6
+ * scenario. Bot keys and plain license keys get no team list.
7
+ */
8
+ import type { Scenario } from "../types";
9
+ export interface TeamScenarioRow {
10
+ id: string;
11
+ app: string;
12
+ scenario: Scenario;
13
+ version: number;
14
+ sharedBy: {
15
+ id: string;
16
+ label: string;
17
+ };
18
+ sharedAt: number;
19
+ updatedAt: number;
20
+ }
21
+ export interface TeamPullResult {
22
+ scenarios: TeamScenarioRow[];
23
+ /** The caller's account id. */
24
+ you: string;
25
+ admin: boolean;
26
+ }
27
+ /**
28
+ * Why a call didn't happen or didn't work:
29
+ * - `no-team`: no sign-in on a Business team (or a plain key).
30
+ * - `no-app`: Buoy can't tell which app this is.
31
+ * - `network`: buoy.gg couldn't be reached.
32
+ * - `refused`: buoy.gg said no; `message` says why.
33
+ */
34
+ export type TeamFailure = {
35
+ ok: false;
36
+ code: "no-team" | "no-app" | "network" | "refused";
37
+ message: string;
38
+ };
39
+ export type TeamCall<T> = {
40
+ ok: true;
41
+ value: T;
42
+ } | TeamFailure;
43
+ export interface TeamClientDeps {
44
+ credential: () => {
45
+ token: string;
46
+ origin: string;
47
+ team: string;
48
+ appId: string | null;
49
+ } | null;
50
+ appId: () => string | null;
51
+ fetch: (url: string, init: {
52
+ method: string;
53
+ headers: Record<string, string>;
54
+ body: string;
55
+ signal?: AbortSignal;
56
+ }) => Promise<{
57
+ status: number;
58
+ json(): Promise<unknown>;
59
+ }>;
60
+ }
61
+ export declare const NO_TEAM_MESSAGE = "Sign in with a Business team account to share scenarios with your team.";
62
+ export declare const NO_APP_MESSAGE = "Buoy can't tell which app this is. Pass signIn={{ appId }} to FloatingDevTools to name it.";
63
+ export declare function defaultTeamClientDeps(): TeamClientDeps;
64
+ /** The app this device's team list belongs to: the one its sign-in names, else the one core found. */
65
+ export declare function teamAppId(deps: TeamClientDeps): string | null;
66
+ /** The team's scenarios for this app. */
67
+ export declare function pullTeamScenarios(deps?: TeamClientDeps): Promise<TeamCall<TeamPullResult>>;
68
+ /**
69
+ * Fields that only mean something on this device. They never leave it:
70
+ * how often it ran here, when someone here last checked it, where it came from.
71
+ */
72
+ export declare function forSharing(scenario: Scenario): Omit<Scenario, "usage" | "source" | "verifiedAt" | "team">;
73
+ /** Share one scenario with the team. A person's tap only: no tool action calls this. */
74
+ export declare function shareTeamScenario(scenario: Scenario, deps?: TeamClientDeps): Promise<TeamCall<TeamScenarioRow>>;
75
+ /** Take one scenario off the team's list. Only its sharer or a team admin may. */
76
+ export declare function removeTeamScenario(id: string, deps?: TeamClientDeps): Promise<TeamCall<true>>;
77
+ //# sourceMappingURL=teamClient.d.ts.map
@@ -48,8 +48,21 @@ export interface ScenarioStep {
48
48
  * - `device` — saved on this device; editable, deletable, exportable.
49
49
  * - `draft` — authored remotely (MCP/desktop) and INERT until a human accepts it
50
50
  * on-device. A draft never runs and never appears as a Run target.
51
+ * - `team` — shared by someone on this app's Business team, pulled from
52
+ * buoy.gg. Read-only on device, like `code`; runs like any other.
51
53
  */
52
- export type ScenarioSource = "code" | "device" | "draft";
54
+ export type ScenarioSource = "code" | "device" | "draft" | "team";
55
+ /** Who shared a team scenario, and when. Only on `team` scenarios. */
56
+ export interface ScenarioTeamInfo {
57
+ sharedBy: {
58
+ id: string;
59
+ label: string;
60
+ };
61
+ sharedAt: number;
62
+ updatedAt: number;
63
+ /** The app it was shared from, which is this app. */
64
+ app: string;
65
+ }
53
66
  export interface Scenario {
54
67
  id: string;
55
68
  /** Placeholders allowed: "Out-of-stock item at store {{storeId}}". */
@@ -99,6 +112,8 @@ export interface Scenario {
99
112
  runs: number;
100
113
  lastRunAt?: number;
101
114
  };
115
+ /** Set on `team` scenarios. */
116
+ team?: ScenarioTeamInfo;
102
117
  }
103
118
  /**
104
119
  * An effect a run actually applied. Recorded per-step AS IT SUCCEEDS, so a
@@ -153,6 +168,12 @@ export interface ActiveScenario {
153
168
  effects: AppliedEffect[];
154
169
  /** Epoch ms after which the runner auto-deactivates. */
155
170
  expiresAt?: number;
171
+ /**
172
+ * The undo steps as they were when it ran. A team scenario can change or
173
+ * go away while it is on, and undo must still run the steps that match
174
+ * what was applied.
175
+ */
176
+ undoSteps?: ScenarioStep[];
156
177
  }
157
178
  export interface StepReport {
158
179
  id: string;