@agimon-ai/doompi-loop 0.0.1-alpha.84 → 0.0.1-alpha.85

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.
@@ -2,217 +2,207 @@ Object.defineProperties(exports, {
2
2
  __esModule: { value: true },
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
- const require_loopView = require("../loopView-CLIBhqYC.cjs");
5
+ const require_loopLaunchers = require("../loopLaunchers-BtcLH_xN.cjs");
6
+ const require_loopView = require("../loopView-C_OPuy8L.cjs");
6
7
  let _agimon_ai_doompi_core_extensionFile = require("@agimon-ai/doompi-core/extensionFile");
7
8
  let _agimon_ai_doompi_minor_mode = require("@agimon-ai/doompi-minor-mode");
9
+ let croner = require("croner");
8
10
  let _agimon_ai_doompi_core_serverFacet = require("@agimon-ai/doompi-core/serverFacet");
9
11
  //#region src/services/serverRuntime/index.ts
10
- const SOURCE = "@agimon-ai/doompi-loop";
11
- const MODE_ID = "loop.active";
12
- const DEFAULT_LAUNCHER_ID = "doompi.default";
13
- const DEFAULT_INTERVAL_SECONDS = 300;
14
- const MIN_INTERVAL_SECONDS = 30;
15
- const MAX_INTERVAL_SECONDS = 3600;
16
- function notify(context, body, level = "info") {
17
- return Promise.resolve(context.client.notify({
18
- body,
19
- level
20
- }));
21
- }
22
- function numberInput(value, fallback) {
23
- if (typeof value !== "string" || value.trim() === "") return fallback;
24
- const parsed = Number(value);
25
- if (!Number.isInteger(parsed) || parsed < MIN_INTERVAL_SECONDS || parsed > MAX_INTERVAL_SECONDS) throw new Error(`Interval must be between ${MIN_INTERVAL_SECONDS} and ${MAX_INTERVAL_SECONDS} seconds.`);
26
- return parsed;
12
+ async function prepareLoop(execution, request, cron) {
13
+ if (request.input !== void 0) return cron ? require_loopView.parseCronLoop(request.input) : require_loopView.parseIntervalLoop(request.input);
14
+ if (request.interactive === false) throw new Error("Loop configuration is required for agent launches.");
15
+ const input = await execution.client.request({
16
+ kind: "input",
17
+ title: "Loop prompt",
18
+ multiline: true
19
+ }, request.signal);
20
+ const prompt = typeof input === "string" ? input.trim() : "";
21
+ if (!prompt || request.signal.aborted) return void 0;
22
+ if (cron) {
23
+ const expression = await execution.client.request({
24
+ kind: "input",
25
+ title: "Cron expression (five fields)",
26
+ initialValue: "0 * * * *"
27
+ }, request.signal);
28
+ if (typeof expression !== "string" || request.signal.aborted) return void 0;
29
+ const timezone = await execution.client.request({
30
+ kind: "input",
31
+ title: "Loop timezone",
32
+ initialValue: "UTC"
33
+ }, request.signal);
34
+ if (typeof timezone !== "string" || request.signal.aborted) return void 0;
35
+ return require_loopView.parseCronLoop({
36
+ prompt,
37
+ cron: expression,
38
+ timezone: timezone.trim() || "UTC"
39
+ });
40
+ }
41
+ const interval = await execution.client.request({
42
+ kind: "input",
43
+ title: "Loop interval in seconds",
44
+ initialValue: "300"
45
+ }, request.signal);
46
+ if (typeof interval !== "string" || request.signal.aborted) return void 0;
47
+ return require_loopView.parseIntervalLoop({
48
+ prompt,
49
+ intervalSeconds: Number(interval.trim() || "300")
50
+ });
27
51
  }
28
52
  function createSessionState(host) {
29
- let launchers;
30
- let defaultRegistration;
31
- let unsubscribe;
32
- let modeOwner;
33
- const modeSelected = () => (host.context.selection.state?.["minor-mode"] ?? []).includes(MODE_ID);
34
- const modeState = () => {
35
- const launcherCount = launchers?.listLaunchers().length ?? 0;
36
- const active = (launchers?.listInstances().length ?? 0) > 0;
37
- const canStart = launcherCount > 0 || !modeSelected();
38
- return {
39
- activation: active ? "active" : "inactive",
40
- condition: launchers?.listInstances().some(({ state }) => state !== "running") ? "queued" : "ready",
41
- ...active ? {
42
- detail: `${launchers?.listInstances().length} active`,
43
- color: "warning"
44
- } : {},
45
- actions: [{
46
- id: "start",
47
- enabled: canStart,
48
- ...canStart ? {} : { disabledReason: "No loop launchers are registered." }
49
- }, {
50
- id: "stop",
51
- enabled: active,
52
- ...active ? {} : { disabledReason: "No loops are active." }
53
- }]
54
- };
55
- };
56
- /**
57
- * The instances the activity dock lists.
58
- *
59
- * An empty list publishes an empty value rather than nothing, because the key
60
- * being present is what keeps the group on screen as a launcher. The group
61
- * declares no `hideWhenEmpty`, so an empty value is a header with no rows.
62
- */
63
- const publishView = () => host.context.client.setStatus(require_loopView.LOOP_VIEW_STATUS_KEY, require_loopView.formatLoopStatusView(launchers?.listInstances() ?? []));
64
- /** Both surfaces a loop shows up on: the minor-mode catalog and the activity dock. */
65
- const publishMode = () => {
66
- modeOwner?.publish();
67
- publishView();
68
- };
69
- const startActivity = async (execution) => {
70
- if (launchers !== void 0) return async () => void 0;
71
- const dependencies = {
72
- generation: `${execution.sessionId}:loop-launchers`,
73
- createInstanceId: () => crypto.randomUUID(),
74
- timestamp: () => (/* @__PURE__ */ new Date()).toISOString()
75
- };
76
- const service = require_loopView.createDoomLoopLaunchersService(dependencies);
77
- const registration = service.register({
78
- id: DEFAULT_LAUNCHER_ID,
79
- source: SOURCE,
80
- label: "Default loop",
81
- description: "Run a prompt in this session on an interval.",
82
- async launch({ instanceId, signal }) {
83
- const promptValue = await execution.client.request({
84
- kind: "input",
85
- title: "Loop prompt",
86
- multiline: true
87
- }, signal);
88
- const prompt = typeof promptValue === "string" ? promptValue.trim() : "";
89
- if (!prompt || signal.aborted) return void 0;
90
- const interval = numberInput(await execution.client.request({
91
- kind: "input",
92
- title: "Loop interval in seconds",
93
- initialValue: String(DEFAULT_INTERVAL_SECONDS)
94
- }, signal), DEFAULT_INTERVAL_SECONDS);
95
- let stopped = false;
96
- const tick = () => {
97
- if (stopped || signal.aborted) return;
98
- execution.session.prompt(prompt).catch((error) => {
99
- notify(execution, `Loop pass failed: ${error instanceof Error ? error.message : String(error)}`, "warning");
100
- });
101
- };
102
- const timer = setInterval(tick, interval * 1e3);
103
- timer.unref?.();
104
- tick();
105
- return {
106
- instanceId,
107
- label: "Default loop",
108
- detail: `every ${interval}s`,
109
- stop() {
110
- stopped = true;
111
- clearInterval(timer);
112
- }
113
- };
114
- }
115
- });
116
- launchers = service;
117
- defaultRegistration = registration;
118
- unsubscribe = service.subscribe(publishMode);
119
- publishMode();
120
- return async () => {
121
- unsubscribe?.();
122
- unsubscribe = void 0;
123
- await registration.dispose("Headless loop activity stopped.");
124
- await service.dispose("Headless loop activity stopped.");
125
- if (launchers === service) launchers = void 0;
126
- if (defaultRegistration === registration) defaultRegistration = void 0;
127
- publishMode();
128
- };
129
- };
130
- modeOwner = (0, _agimon_ai_doompi_minor_mode.defineMinorMode)({
53
+ const launchers = require_loopView.createDoomLoopLaunchersService({
54
+ generation: `${host.context.sessionId}:loop-launchers`,
55
+ createInstanceId: () => crypto.randomUUID(),
56
+ timestamp: () => (/* @__PURE__ */ new Date()).toISOString()
57
+ });
58
+ let disposed = false;
59
+ const modeSelected = () => !disposed && (host.context.selection.state?.["minor-mode"] ?? []).includes("loop.active");
60
+ const publishView = () => host.context.client.setStatus(require_loopView.LOOP_VIEW_STATUS_KEY, require_loopView.formatLoopStatusView(launchers.listInstances()));
61
+ const modeOwner = (0, _agimon_ai_doompi_minor_mode.defineMinorMode)({
131
62
  descriptor: {
132
- source: SOURCE,
133
- id: MODE_ID,
63
+ source: require_loopView.PACKAGE_SOURCE,
64
+ id: require_loopView.MODE_STATUS_ID,
134
65
  label: "Loop",
135
- description: "Session-scoped recurring prompt loops.",
66
+ description: "Give the agent tools to create, inspect, and stop session loops.",
136
67
  order: 60,
137
68
  actions: [{
138
- id: "start",
139
- label: "Start",
140
- description: "Start a loop with a registered launcher.",
69
+ id: "activate",
70
+ label: "Activate",
71
+ description: "Expose loop tools to the agent.",
141
72
  contexts: ["headless"],
142
- parameters: [{
143
- name: "launcherId",
144
- label: "Launcher",
145
- kind: "string",
146
- required: true,
147
- minLength: 1
148
- }, {
149
- name: "instanceId",
150
- label: "Instance ID",
151
- kind: "string",
152
- required: false,
153
- minLength: 1
154
- }]
73
+ parameters: []
155
74
  }, {
156
- id: "stop",
157
- label: "Stop",
158
- description: "Stop one active loop instance.",
75
+ id: "deactivate",
76
+ label: "Deactivate",
77
+ description: "Hide agent loop tools. Existing loops and manual controls remain available.",
159
78
  contexts: ["headless"],
160
- parameters: [{
161
- name: "instanceId",
162
- label: "Instance ID",
163
- kind: "string",
164
- required: true,
165
- minLength: 1
166
- }, {
167
- name: "reason",
168
- label: "Reason",
169
- kind: "string",
170
- required: false,
171
- minLength: 1
172
- }]
79
+ parameters: []
173
80
  }]
174
81
  },
175
- state: modeState,
176
- async handleAction(_runtime, actionId, argumentsValue, { signal }) {
82
+ state: () => ({
83
+ activation: modeSelected() ? "active" : "inactive",
84
+ condition: "ready",
85
+ ...modeSelected() ? {
86
+ detail: `${launchers.listInstances().length} loops`,
87
+ color: "accent"
88
+ } : {},
89
+ actions: [{
90
+ id: "activate",
91
+ enabled: !modeSelected(),
92
+ ...modeSelected() ? { disabledReason: "Loop mode is already active." } : {}
93
+ }, {
94
+ id: "deactivate",
95
+ enabled: modeSelected(),
96
+ ...modeSelected() ? {} : { disabledReason: "Loop mode is not active." }
97
+ }]
98
+ }),
99
+ async handleAction(_runtime, actionId, _arguments, { signal }) {
177
100
  signal.throwIfAborted();
178
- if (actionId === "start" && !launchers) {
179
- const modes = (host.context.selection.state?.["minor-mode"] ?? []).filter((mode) => mode !== MODE_ID);
180
- await host.changeSelection({
181
- axis: "state",
182
- key: "minor-mode",
183
- values: [...modes, MODE_ID]
184
- });
185
- }
186
- if (!launchers) throw new Error("Loop activity is not active.");
187
- if (actionId === "start") {
188
- const instance = await launchers.launch(String(argumentsValue.launcherId ?? ""), argumentsValue.instanceId ? { instanceId: String(argumentsValue.instanceId) } : {});
189
- publishMode();
190
- return { message: instance ? `Loop '${instance.instanceId}' started.` : "Loop launch was cancelled." };
191
- }
192
- if (actionId === "stop") {
193
- const stopped = await launchers.stop(String(argumentsValue.instanceId ?? ""), String(argumentsValue.reason ?? "Stopped through minor_mode."));
194
- if (stopped && launchers.listInstances().length === 0) {
195
- const modes = (host.context.selection.state?.["minor-mode"] ?? []).filter((mode) => mode !== MODE_ID);
196
- await host.changeSelection({
197
- axis: "state",
198
- key: "minor-mode",
199
- values: modes
101
+ if (actionId !== "activate" && actionId !== "deactivate") throw new Error(`Unknown loop mode action: ${actionId}`);
102
+ const modes = (host.context.selection.state?.["minor-mode"] ?? []).filter((mode) => mode !== require_loopView.MODE_STATUS_ID);
103
+ await host.changeSelection({
104
+ axis: "state",
105
+ key: "minor-mode",
106
+ values: actionId === "activate" ? [...modes, require_loopView.MODE_STATUS_ID] : modes
107
+ });
108
+ modeOwner.publish();
109
+ return { message: actionId === "activate" ? "Loop tools activated." : "Loop tools deactivated. Existing loops are unchanged." };
110
+ }
111
+ }).createOwner(void 0);
112
+ const publish = () => {
113
+ if (disposed) return;
114
+ modeOwner.publish();
115
+ publishView();
116
+ };
117
+ const unsubscribe = launchers.subscribe(publish);
118
+ let submitting = false;
119
+ for (const cron of [false, true]) launchers.register({
120
+ id: cron ? "doompi.cron" : "doompi.default",
121
+ source: require_loopView.PACKAGE_SOURCE,
122
+ label: cron ? "Cron loop" : "Default loop",
123
+ description: cron ? "Run a prompt on a five-field cron schedule." : "Run a prompt in this session on an interval.",
124
+ inputSchema: { ...cron ? require_loopView.CronLoopSchema : require_loopView.IntervalLoopSchema },
125
+ async launch(request) {
126
+ const execution = host.context;
127
+ const prepared = await prepareLoop(execution, request, cron);
128
+ if (!prepared || request.signal.aborted) return void 0;
129
+ let stopped = false;
130
+ const tick = async () => {
131
+ if (stopped || request.signal.aborted || submitting) return;
132
+ submitting = true;
133
+ try {
134
+ const activity = await execution.session.activity();
135
+ if (stopped || request.signal.aborted || !activity.isIdle || activity.hasPendingMessages) return;
136
+ await execution.session.prompt(prepared.prompt);
137
+ } catch (error) {
138
+ await execution.client.notify({
139
+ body: `Loop pass failed: ${error instanceof Error ? error.message : String(error)}`,
140
+ level: "warning"
200
141
  });
142
+ } finally {
143
+ submitting = false;
144
+ }
145
+ };
146
+ let stopTimer;
147
+ let detail;
148
+ if ("cron" in prepared) {
149
+ const job = new croner.Cron(prepared.cron, {
150
+ timezone: prepared.timezone,
151
+ unref: true
152
+ }, tick);
153
+ if (job.nextRun() === null) {
154
+ job.stop();
155
+ throw new Error("Cron expression has no future run.");
201
156
  }
202
- publishMode();
203
- return { message: stopped ? "Loop stopped." : "Loop instance was not active." };
157
+ stopTimer = () => {
158
+ job.stop();
159
+ };
160
+ detail = `${prepared.cron} (${prepared.timezone})`;
161
+ } else {
162
+ const timer = setInterval(() => {
163
+ tick();
164
+ }, prepared.intervalSeconds * 1e3);
165
+ timer.unref?.();
166
+ stopTimer = () => clearInterval(timer);
167
+ detail = `every ${prepared.intervalSeconds}s`;
168
+ tick();
204
169
  }
205
- throw new Error(`Unknown loop mode action: ${actionId}`);
170
+ return {
171
+ instanceId: request.instanceId,
172
+ label: cron ? "Cron loop" : "Default loop",
173
+ detail,
174
+ stop() {
175
+ stopped = true;
176
+ stopTimer();
177
+ }
178
+ };
206
179
  }
207
- }).createOwner(void 0);
180
+ });
208
181
  return {
209
- services: [(0, _agimon_ai_doompi_minor_mode.serverMinorModes)([modeOwner])],
182
+ loopTools: require_loopView.createLoopTools(() => launchers, () => {
183
+ if (!modeSelected()) throw new Error("Enable Loop minor mode before using agent loop tools.");
184
+ }).map((tool) => ({
185
+ ...tool,
186
+ when: {
187
+ state: { "minor-mode": require_loopView.MODE_STATUS_ID },
188
+ attribution: {
189
+ kind: "minor",
190
+ mode: require_loopView.MODE_STATUS_ID
191
+ }
192
+ }
193
+ })),
194
+ services: [(context) => {
195
+ context.plugin((provider) => {
196
+ provider.provide(require_loopLaunchers.DOOM_LOOP_LAUNCHERS_SERVICE, launchers);
197
+ });
198
+ context.effect(() => host.subscribeSelection(publish));
199
+ }, (0, _agimon_ai_doompi_minor_mode.serverMinorModes)([modeOwner])],
210
200
  resources: [{
211
201
  when: {
212
- state: { "minor-mode": MODE_ID },
202
+ state: { "minor-mode": require_loopView.MODE_STATUS_ID },
213
203
  attribution: {
214
204
  kind: "minor",
215
- mode: MODE_ID
205
+ mode: require_loopView.MODE_STATUS_ID
216
206
  }
217
207
  },
218
208
  name: "doompi-use-loop",
@@ -220,29 +210,19 @@ function createSessionState(host) {
220
210
  read: () => (0, _agimon_ai_doompi_core_serverFacet.readPackageResource)(require("url").pathToFileURL(__filename).href, "src/prompts/doompi-use-loop/SKILL.md")
221
211
  }],
222
212
  activities: [{
223
- when: {
224
- state: { "minor-mode": MODE_ID },
225
- attribution: {
226
- kind: "minor",
227
- mode: MODE_ID
228
- }
229
- },
230
213
  name: "doompi-loop",
231
- start: startActivity
214
+ async start() {
215
+ publish();
216
+ return async () => {
217
+ await launchers.stopAll("Session loop activity stopped.");
218
+ publish();
219
+ };
220
+ }
232
221
  }],
233
222
  commands: [{
234
223
  name: require_loopView.START_COMMAND_NAME,
235
- description: "Start a registered loop.",
224
+ description: "Start a registered loop manually.",
236
225
  async execute(args, execution) {
237
- if (!launchers) {
238
- const modes = host.context.selection.state?.["minor-mode"] ?? [];
239
- await host.changeSelection({
240
- axis: "state",
241
- key: "minor-mode",
242
- values: [...modes, MODE_ID]
243
- });
244
- }
245
- if (!launchers) throw new Error("Loop activity is not active.");
246
226
  const available = launchers.listLaunchers();
247
227
  const selected = args.trim() || await execution.client.request({
248
228
  kind: "select",
@@ -252,47 +232,57 @@ function createSessionState(host) {
252
232
  value: entry.id
253
233
  }))
254
234
  });
255
- const launcherId = args.trim() || (typeof selected === "string" ? selected : "");
256
- if (!launcherId) return;
257
- const instance = await launchers.launch(launcherId);
258
- publishMode();
259
- await notify(execution, instance ? `Loop '${instance.instanceId}' started.` : "Loop launch was cancelled.");
235
+ if (typeof selected !== "string" || !selected) return;
236
+ const instance = await launchers.launch(selected);
237
+ await execution.client.notify({
238
+ body: instance ? `Loop '${instance.instanceId}' started.` : "Loop launch was cancelled.",
239
+ level: "info"
240
+ });
260
241
  }
261
242
  }, {
262
243
  name: require_loopView.LIST_COMMAND_NAME,
263
- description: "List and stop active loops.",
264
- async execute(_args, execution) {
265
- const instances = launchers?.listInstances() ?? [];
266
- await notify(execution, instances.length === 0 ? "No loops are active." : JSON.stringify(instances, null, 2));
244
+ description: "List or stop session loops. Usage: /loops [stop instanceId].",
245
+ async execute(args, execution) {
246
+ const parts = args.trim().split(/\s+/u);
247
+ if (parts[0] === "stop" && parts.length === 2) {
248
+ const stopped = await launchers.stop(parts[1], "Stopped manually.");
249
+ await execution.client.notify({
250
+ body: stopped ? "Loop stopped." : "Loop instance was not active.",
251
+ level: "info"
252
+ });
253
+ return;
254
+ }
255
+ if (args.trim()) throw new Error("Usage: /loops [stop instanceId]");
256
+ const instances = launchers.listInstances();
257
+ await execution.client.notify({
258
+ body: instances.length === 0 ? "No loops are active." : JSON.stringify(instances, null, 2),
259
+ level: "info"
260
+ });
267
261
  }
268
262
  }],
269
263
  hooks: [{
270
264
  event: "session_start",
271
265
  async handle() {
272
- publishView();
266
+ publish();
273
267
  }
274
268
  }, {
275
- when: {
276
- state: { "minor-mode": MODE_ID },
277
- attribution: {
278
- kind: "minor",
279
- mode: MODE_ID
280
- }
281
- },
282
269
  event: "session_shutdown",
283
270
  async handle() {
284
- await launchers?.stopAll("Headless session shutdown.");
271
+ await launchers.stopAll("Headless session shutdown.");
285
272
  }
286
273
  }],
274
+ onStart: publish,
287
275
  async onDispose() {
288
- await defaultRegistration?.dispose("Headless loop facet disposed.");
289
- await launchers?.dispose("Headless loop facet disposed.");
276
+ if (disposed) return;
277
+ disposed = true;
278
+ unsubscribe();
279
+ await launchers.dispose("Headless loop facet disposed.");
290
280
  }
291
281
  };
292
282
  }
293
283
  //#endregion
294
284
  //#region src/extensions/workspaces/sessions/(backend)/_lib/runtime.server.ts
295
- var runtime_server_default = (({ agent }) => agent ? createSessionState(agent) : {});
285
+ var runtime_server_default = (({ agent }) => agent ? createSessionState(agent) : { loopTools: [] });
296
286
  //#endregion
297
287
  //#region src/extensions/workspaces/sessions/(backend)/root.server.ts
298
288
  var root_server_default = (0, _agimon_ai_doompi_core_extensionFile.defineRoot)((context) => {
@@ -322,14 +312,23 @@ var session_start_server_default = (0, _agimon_ai_doompi_core_extensionFile.defi
322
312
  //#region src/extensions/workspaces/sessions/(backend)/resource/doompi-use-loop.server.ts
323
313
  var doompi_use_loop_server_default = (0, _agimon_ai_doompi_core_extensionFile.defineRoutedContribution)((context) => context.root.resources?.[0], { cardinality: "optional" });
324
314
  //#endregion
315
+ //#region src/extensions/workspaces/sessions/(backend)/tool/loop-list.server.ts
316
+ var loop_list_server_default = (0, _agimon_ai_doompi_core_extensionFile.defineTool)((context) => context.root.loopTools[0]);
317
+ //#endregion
318
+ //#region src/extensions/workspaces/sessions/(backend)/tool/loop-start.server.ts
319
+ var loop_start_server_default = (0, _agimon_ai_doompi_core_extensionFile.defineTool)((context) => context.root.loopTools[1]);
320
+ //#endregion
321
+ //#region src/extensions/workspaces/sessions/(backend)/tool/loop-stop.server.ts
322
+ var loop_stop_server_default = (0, _agimon_ai_doompi_core_extensionFile.defineTool)((context) => context.root.loopTools[2]);
323
+ //#endregion
325
324
  //#region generated/server.ts
326
325
  const at = (value, context) => typeof value === "function" ? value(context) : value;
327
326
  /* v8 ignore next -- generated normalization branch */
328
327
  const defined = (values) => values.filter((value) => value !== void 0);
329
- const via = (identity, value) => ({
328
+ const via = (identity, value) => value === void 0 ? void 0 : {
330
329
  ...identity,
331
330
  ...value
332
- });
331
+ };
333
332
  const facet = (0, _agimon_ai_doompi_core_serverFacet.defineServerPlugin)({
334
333
  name: "@agimon-ai/doompi-loop",
335
334
  session: async (context) => {
@@ -349,6 +348,13 @@ const facet = (0, _agimon_ai_doompi_core_serverFacet.defineServerPlugin)({
349
348
  get resources() {
350
349
  return defined([at(doompi_use_loop_server_default, contextSession)]);
351
350
  },
351
+ get tools() {
352
+ return [
353
+ via({ name: "loop_list" }, at(loop_list_server_default, contextSession)),
354
+ via({ name: "loop_start" }, at(loop_start_server_default, contextSession)),
355
+ via({ name: "loop_stop" }, at(loop_stop_server_default, contextSession))
356
+ ];
357
+ },
352
358
  services: [...scopeSession.services ?? []],
353
359
  get activities() {
354
360
  return [...scopeSession.activities ?? []];