@yuzc-001/grasp 0.6.6

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 (78) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +327 -0
  3. package/README.zh-CN.md +324 -0
  4. package/examples/README.md +31 -0
  5. package/examples/claude-desktop.json +8 -0
  6. package/examples/codex-config.toml +4 -0
  7. package/grasp.skill +0 -0
  8. package/index.js +87 -0
  9. package/package.json +48 -0
  10. package/scripts/grasp_openclaw_ctl.sh +122 -0
  11. package/scripts/run-search-benchmark.mjs +287 -0
  12. package/scripts/update-star-history.mjs +274 -0
  13. package/skill/SKILL.md +61 -0
  14. package/skill/references/tools.md +306 -0
  15. package/src/cli/auto-configure.js +116 -0
  16. package/src/cli/cmd-connect.js +148 -0
  17. package/src/cli/cmd-explain.js +42 -0
  18. package/src/cli/cmd-logs.js +55 -0
  19. package/src/cli/cmd-status.js +119 -0
  20. package/src/cli/config.js +27 -0
  21. package/src/cli/detect-chrome.js +58 -0
  22. package/src/grasp/handoff/events.js +67 -0
  23. package/src/grasp/handoff/persist.js +48 -0
  24. package/src/grasp/handoff/state.js +28 -0
  25. package/src/grasp/page/capture.js +34 -0
  26. package/src/grasp/page/state.js +273 -0
  27. package/src/grasp/verify/evidence.js +40 -0
  28. package/src/grasp/verify/pipeline.js +52 -0
  29. package/src/layer1-bridge/chrome.js +416 -0
  30. package/src/layer1-bridge/webmcp.js +143 -0
  31. package/src/layer2-perception/hints.js +284 -0
  32. package/src/layer3-action/actions.js +400 -0
  33. package/src/runtime/browser-instance.js +65 -0
  34. package/src/runtime/truth/model.js +94 -0
  35. package/src/runtime/truth/snapshot.js +51 -0
  36. package/src/server/affordances.js +47 -0
  37. package/src/server/audit.js +122 -0
  38. package/src/server/boss-fast-path.js +164 -0
  39. package/src/server/boundary-guard.js +53 -0
  40. package/src/server/content.js +97 -0
  41. package/src/server/continuity.js +256 -0
  42. package/src/server/engine-selection.js +29 -0
  43. package/src/server/entry-orchestrator.js +115 -0
  44. package/src/server/error-codes.js +7 -0
  45. package/src/server/explain-share-card.js +113 -0
  46. package/src/server/fast-path-router.js +134 -0
  47. package/src/server/form-runtime.js +602 -0
  48. package/src/server/form-tasks.js +254 -0
  49. package/src/server/gateway-response.js +62 -0
  50. package/src/server/index.js +22 -0
  51. package/src/server/observe.js +52 -0
  52. package/src/server/page-projection.js +31 -0
  53. package/src/server/page-state.js +27 -0
  54. package/src/server/postconditions.js +128 -0
  55. package/src/server/prompt-assembly.js +148 -0
  56. package/src/server/responses.js +44 -0
  57. package/src/server/route-boundary.js +174 -0
  58. package/src/server/route-policy.js +168 -0
  59. package/src/server/runtime-confirmation.js +87 -0
  60. package/src/server/runtime-status.js +7 -0
  61. package/src/server/share-artifacts.js +284 -0
  62. package/src/server/state.js +132 -0
  63. package/src/server/structured-extraction.js +131 -0
  64. package/src/server/surface-prompts.js +166 -0
  65. package/src/server/task-frame.js +11 -0
  66. package/src/server/tasks/search-task.js +321 -0
  67. package/src/server/tools.actions.js +1361 -0
  68. package/src/server/tools.form.js +526 -0
  69. package/src/server/tools.gateway.js +757 -0
  70. package/src/server/tools.handoff.js +210 -0
  71. package/src/server/tools.js +20 -0
  72. package/src/server/tools.legacy.js +983 -0
  73. package/src/server/tools.strategy.js +250 -0
  74. package/src/server/tools.task-surface.js +66 -0
  75. package/src/server/tools.workspace.js +873 -0
  76. package/src/server/workspace-runtime.js +1138 -0
  77. package/src/server/workspace-tasks.js +735 -0
  78. package/start-chrome.bat +84 -0
@@ -0,0 +1,1138 @@
1
+ import { ACTION_NOT_VERIFIED, LOADING_PENDING } from './error-codes.js';
2
+ import { verifyGenericAction, verifyTypeResult } from './postconditions.js';
3
+ import { classifyWorkspaceSurface, getWorkspaceStatus, summarizeWorkspaceSnapshot } from './workspace-tasks.js';
4
+ import { clickByHintId, typeByHintId } from '../layer3-action/actions.js';
5
+
6
+ function compactText(value) {
7
+ return String(value ?? '').replace(/\s+/g, ' ').trim();
8
+ }
9
+
10
+ function normalizeLabel(value) {
11
+ return compactText(value).toLowerCase();
12
+ }
13
+
14
+ function pick(snapshot, camelKey, snakeKey, fallback = null) {
15
+ if (snapshot?.[camelKey] !== undefined) return snapshot[camelKey];
16
+ if (snapshot?.[snakeKey] !== undefined) return snapshot[snakeKey];
17
+ return fallback;
18
+ }
19
+
20
+ function getLiveItems(snapshot) {
21
+ const items = pick(snapshot, 'liveItems', 'live_items', []);
22
+ return Array.isArray(items) ? items : [];
23
+ }
24
+
25
+ function getComposer(snapshot) {
26
+ const composer = pick(snapshot, 'composer', 'composer', null);
27
+ return composer && typeof composer === 'object' ? composer : null;
28
+ }
29
+
30
+ function getActionControls(snapshot) {
31
+ const controls = pick(snapshot, 'actionControls', 'action_controls', []);
32
+ return Array.isArray(controls) ? controls : [];
33
+ }
34
+
35
+ function getSendActionControls(snapshot) {
36
+ return getActionControls(snapshot).filter((control) => control?.action_kind === 'send');
37
+ }
38
+
39
+ function getWorkspaceExecuteSignals(snapshot) {
40
+ const summary = pick(snapshot, 'summary', 'summary', null) ?? {};
41
+ const composer = getComposer(snapshot);
42
+ const blockingModals = pick(snapshot, 'blockingModals', 'blocking_modals', []);
43
+ const sendControl = getSendActionControls(snapshot)[0] ?? null;
44
+
45
+ return {
46
+ loadingShell: isLoadingShell(snapshot),
47
+ blockingModalCount: Array.isArray(blockingModals) ? blockingModals.length : 0,
48
+ draftPresent: composer?.draft_present === true || summary?.draft_present === true,
49
+ activeItemStable: summary?.outcome_signals?.active_item_stable === true || summary?.active_item_stable === true,
50
+ sendControl,
51
+ };
52
+ }
53
+
54
+ function canExecuteWorkspaceSend(snapshot) {
55
+ const signals = getWorkspaceExecuteSignals(snapshot);
56
+
57
+ return signals.loadingShell === false
58
+ && signals.blockingModalCount === 0
59
+ && signals.draftPresent === true
60
+ && signals.activeItemStable === true
61
+ && signals.sendControl !== null;
62
+ }
63
+
64
+ function getWorkspaceSurface(snapshot) {
65
+ return pick(snapshot, 'workspaceSurface', 'workspace_surface', null) ?? classifyWorkspaceSurface(snapshot);
66
+ }
67
+
68
+ function isLoadingShell(snapshot) {
69
+ return pick(snapshot, 'loadingShell', 'loading_shell', false) === true
70
+ || getWorkspaceSurface(snapshot) === 'loading_shell';
71
+ }
72
+
73
+ function buildUnresolved(reason, requestedLabel, matches = []) {
74
+ return {
75
+ reason,
76
+ requested_label: compactText(requestedLabel),
77
+ matches: matches.map((item) => ({
78
+ label: item.label,
79
+ hint_id: item.hint_id ?? null,
80
+ })),
81
+ };
82
+ }
83
+
84
+ function buildSelectionUnresolved(reason, requestedLabel, matches = [], recoveryHint = null) {
85
+ return {
86
+ ...buildUnresolved(reason, requestedLabel, matches),
87
+ recovery_hint: recoveryHint,
88
+ };
89
+ }
90
+
91
+ function normalizeWorkspaceSnapshot(snapshot) {
92
+ if (!snapshot || typeof snapshot !== 'object') {
93
+ return snapshot;
94
+ }
95
+
96
+ const summary = summarizeWorkspaceSnapshot(snapshot);
97
+ return {
98
+ ...snapshot,
99
+ summary: snapshot.summary !== undefined ? snapshot.summary : summary,
100
+ summary_text: summary.summary,
101
+ outcome_signals: summary.outcome_signals,
102
+ loading_shell: summary.loading_shell,
103
+ workspace_surface: summary.workspace_surface,
104
+ };
105
+ }
106
+
107
+ function buildUnsupportedWorkspace(requestedLabel) {
108
+ return buildUnresolved('unsupported_workspace', requestedLabel);
109
+ }
110
+
111
+ function buildWorkspaceExecuteBlocked(reason, requestedLabel, snapshot) {
112
+ return {
113
+ status: 'blocked',
114
+ blocked: true,
115
+ executed: false,
116
+ reason,
117
+ unresolved: null,
118
+ failure: null,
119
+ action: {
120
+ kind: 'execute_action',
121
+ status: 'blocked',
122
+ },
123
+ snapshot: normalizeWorkspaceSnapshot(snapshot ?? null),
124
+ workspace: null,
125
+ summary: null,
126
+ requested_label: compactText(requestedLabel),
127
+ };
128
+ }
129
+
130
+ function buildWorkspaceExecuteUnresolved(reason, requestedLabel, matches = [], recoveryHint = null, snapshot = null) {
131
+ return {
132
+ status: 'unresolved',
133
+ blocked: false,
134
+ executed: false,
135
+ reason,
136
+ unresolved: {
137
+ reason,
138
+ requested_label: compactText(requestedLabel),
139
+ recovery_hint: recoveryHint,
140
+ matches,
141
+ },
142
+ failure: null,
143
+ action: {
144
+ kind: 'execute_action',
145
+ status: 'unresolved',
146
+ },
147
+ snapshot: normalizeWorkspaceSnapshot(snapshot ?? null),
148
+ workspace: null,
149
+ summary: null,
150
+ requested_label: compactText(requestedLabel),
151
+ };
152
+ }
153
+
154
+ function buildWorkspaceExecuteFailed(failure, snapshot) {
155
+ return {
156
+ status: 'failed',
157
+ blocked: false,
158
+ executed: true,
159
+ reason: 'verification_failed',
160
+ unresolved: null,
161
+ failure,
162
+ action: {
163
+ kind: 'execute_action',
164
+ status: 'failed',
165
+ },
166
+ snapshot: normalizeWorkspaceSnapshot(snapshot ?? null),
167
+ workspace: null,
168
+ summary: null,
169
+ };
170
+ }
171
+
172
+ function getSelectionMatchLabel(item) {
173
+ return normalizeLabel(item?.normalized_label ?? item?.label);
174
+ }
175
+
176
+ function getSelectionSnapshotDetails(snapshot) {
177
+ const summary = summarizeWorkspaceSnapshot(snapshot ?? {});
178
+ const activeItem = pick(snapshot, 'activeItem', 'active_item', null)
179
+ ?? (summary.active_item_label ? { label: summary.active_item_label } : null);
180
+ const detailAlignment = pick(snapshot, 'detailAlignment', 'detail_alignment', summary.detail_alignment);
181
+ const selectionWindow = pick(snapshot, 'selectionWindow', 'selection_window', summary.selection_window);
182
+
183
+ return {
184
+ summary,
185
+ activeItem,
186
+ detailAlignment,
187
+ selectionWindow,
188
+ };
189
+ }
190
+
191
+ function resolveWorkspaceSelection(snapshot, requestedLabel) {
192
+ if (isLoadingShell(snapshot)) {
193
+ return {
194
+ item: null,
195
+ matches: [],
196
+ unresolved: buildSelectionUnresolved('loading_shell', requestedLabel, [], 'reinspect_workspace'),
197
+ };
198
+ }
199
+
200
+ const liveItems = getLiveItems(snapshot);
201
+ const normalized = normalizeLabel(requestedLabel);
202
+ const matches = liveItems.filter((item) => getSelectionMatchLabel(item) === normalized);
203
+
204
+ if (matches.length > 1) {
205
+ return {
206
+ item: null,
207
+ matches,
208
+ unresolved: buildSelectionUnresolved('ambiguous_item', requestedLabel, matches, 'scroll_list'),
209
+ };
210
+ }
211
+
212
+ if (matches.length === 0) {
213
+ return {
214
+ item: null,
215
+ matches: [],
216
+ unresolved: buildSelectionUnresolved(
217
+ 'not_in_visible_window',
218
+ requestedLabel,
219
+ [],
220
+ liveItems.length > 0 ? 'scroll_list' : 'reinspect_workspace',
221
+ ),
222
+ };
223
+ }
224
+
225
+ return {
226
+ item: matches[0],
227
+ matches,
228
+ };
229
+ }
230
+
231
+ function buildSelectionEvidence({
232
+ requestedLabel,
233
+ item,
234
+ summary,
235
+ activeItem,
236
+ detailAlignment,
237
+ selectionWindow,
238
+ recoveryHint,
239
+ matches = [],
240
+ }) {
241
+ return {
242
+ requested_label: compactText(requestedLabel),
243
+ selected_item: item ? {
244
+ label: item.label ?? null,
245
+ hint_id: item.hint_id ?? null,
246
+ selected: item.selected === true,
247
+ } : null,
248
+ active_item: activeItem ? {
249
+ label: activeItem.label ?? null,
250
+ hint_id: activeItem.hint_id ?? null,
251
+ selected: activeItem.selected === true,
252
+ } : null,
253
+ detail_alignment: detailAlignment,
254
+ selection_window: selectionWindow,
255
+ recovery_hint: recoveryHint ?? null,
256
+ match_count: matches.length,
257
+ summary: summary.summary,
258
+ };
259
+ }
260
+
261
+ export function resolveLiveItem(snapshot, requestedLabel) {
262
+ if (isLoadingShell(snapshot)) {
263
+ return {
264
+ item: null,
265
+ ambiguous: false,
266
+ matches: [],
267
+ unresolved: buildUnresolved('loading_shell', requestedLabel),
268
+ };
269
+ }
270
+
271
+ const liveItems = getLiveItems(snapshot);
272
+ const normalized = normalizeLabel(requestedLabel);
273
+ const matches = liveItems.filter((item) => normalizeLabel(item?.normalized_label ?? item?.label) === normalized);
274
+ const hintBacked = matches.filter((item) => compactText(item?.hint_id));
275
+ const identitySensitive = matches.length > 1 && hintBacked.length > 0;
276
+
277
+ if (hintBacked.length === 1) {
278
+ return {
279
+ item: hintBacked[0],
280
+ ambiguous: false,
281
+ matches,
282
+ identity_sensitive: identitySensitive,
283
+ };
284
+ }
285
+
286
+ if (matches.length === 1) {
287
+ return {
288
+ item: matches[0],
289
+ ambiguous: false,
290
+ matches,
291
+ identity_sensitive: identitySensitive,
292
+ };
293
+ }
294
+
295
+ if (matches.length > 1) {
296
+ return {
297
+ item: null,
298
+ ambiguous: true,
299
+ matches,
300
+ identity_sensitive: identitySensitive,
301
+ unresolved: buildUnresolved('ambiguous_item', requestedLabel, hintBacked.length > 0 ? hintBacked : matches),
302
+ };
303
+ }
304
+
305
+ if (getWorkspaceSurface(snapshot) == null) {
306
+ return {
307
+ item: null,
308
+ ambiguous: false,
309
+ matches: [],
310
+ identity_sensitive: identitySensitive,
311
+ unresolved: buildUnsupportedWorkspace(requestedLabel),
312
+ };
313
+ }
314
+
315
+ return {
316
+ item: null,
317
+ ambiguous: false,
318
+ matches: [],
319
+ identity_sensitive: identitySensitive,
320
+ unresolved: buildUnresolved('no_live_target', requestedLabel),
321
+ };
322
+ }
323
+
324
+ export function resolveComposer(snapshot) {
325
+ if (isLoadingShell(snapshot)) {
326
+ return {
327
+ composer: null,
328
+ ambiguous: false,
329
+ unresolved: buildUnresolved('loading_shell', 'composer'),
330
+ };
331
+ }
332
+
333
+ const composer = getComposer(snapshot);
334
+ if (composer) {
335
+ return {
336
+ composer,
337
+ ambiguous: false,
338
+ };
339
+ }
340
+
341
+ if (getWorkspaceSurface(snapshot) == null) {
342
+ return {
343
+ composer: null,
344
+ ambiguous: false,
345
+ unresolved: buildUnsupportedWorkspace('composer'),
346
+ };
347
+ }
348
+
349
+ return {
350
+ composer: null,
351
+ ambiguous: false,
352
+ unresolved: buildUnresolved('no_live_target', 'composer'),
353
+ };
354
+ }
355
+
356
+ export function createWorkspaceWriteEvidence({ kind, target }) {
357
+ return {
358
+ kind,
359
+ target,
360
+ autosave_possible: true,
361
+ write_side_effect: 'draft_mutation_possible',
362
+ };
363
+ }
364
+
365
+ export async function verifySelectionResult({
366
+ snapshot,
367
+ item,
368
+ identitySensitive = false,
369
+ }) {
370
+ const summary = summarizeWorkspaceSnapshot(snapshot ?? {});
371
+ const liveItems = getLiveItems(snapshot);
372
+ const normalizedLabel = normalizeLabel(item?.label);
373
+ const activeItem = pick(snapshot, 'activeItem', 'active_item', null);
374
+ const activeLabel = compactText(activeItem?.label ?? summary.active_item_label ?? '');
375
+ const activeMatch = normalizeLabel(activeLabel) === normalizedLabel;
376
+ const activeHintMatch = Boolean(compactText(activeItem?.hint_id)) && compactText(activeItem?.hint_id) === compactText(item?.hint_id);
377
+ const selectedMatch = liveItems.some((liveItem) => (
378
+ liveItem?.selected === true
379
+ && normalizeLabel(liveItem?.normalized_label ?? liveItem?.label) === normalizedLabel
380
+ && compactText(liveItem?.hint_id) === compactText(item?.hint_id)
381
+ ));
382
+ const detailAlignment = pick(snapshot, 'detailAlignment', 'detail_alignment', summary.detail_alignment);
383
+ const selectionWindow = pick(snapshot, 'selectionWindow', 'selection_window', summary.selection_window);
384
+
385
+ if ((selectedMatch || activeHintMatch || (activeMatch && !identitySensitive)) && detailAlignment !== 'mismatch' && selectionWindow !== 'not_found') {
386
+ return {
387
+ ok: true,
388
+ evidence: {
389
+ target: item?.label ?? null,
390
+ hint_id: item?.hint_id ?? null,
391
+ active_item_label: activeLabel || null,
392
+ active_item_hint_id: compactText(activeItem?.hint_id) || null,
393
+ detail_alignment: detailAlignment,
394
+ selection_window: selectionWindow,
395
+ active_match: activeMatch,
396
+ active_hint_match: activeHintMatch,
397
+ selected_match: selectedMatch,
398
+ identity_sensitive: identitySensitive,
399
+ summary: summary.summary,
400
+ },
401
+ };
402
+ }
403
+
404
+ return {
405
+ ok: false,
406
+ error_code: ACTION_NOT_VERIFIED,
407
+ retryable: true,
408
+ suggested_next_step: 'reverify',
409
+ evidence: {
410
+ target: item?.label ?? null,
411
+ hint_id: item?.hint_id ?? null,
412
+ active_item_label: activeLabel || null,
413
+ active_item_hint_id: compactText(activeItem?.hint_id) || null,
414
+ detail_alignment: detailAlignment,
415
+ selection_window: selectionWindow,
416
+ active_match: activeMatch,
417
+ active_hint_match: activeHintMatch,
418
+ selected_match: selectedMatch,
419
+ identity_sensitive: identitySensitive,
420
+ summary: summary.summary,
421
+ },
422
+ };
423
+ }
424
+
425
+ export async function verifyActionOutcome({
426
+ page,
427
+ kind,
428
+ target,
429
+ hintId,
430
+ expectedText,
431
+ allowPageChange = false,
432
+ prevUrl = null,
433
+ prevDomRevision = null,
434
+ prevActiveId = null,
435
+ newDomRevision = null,
436
+ outcomeSignals = null,
437
+ snapshot = null,
438
+ }) {
439
+ const loadingShell = snapshot
440
+ ? pick(snapshot, 'loadingShell', 'loading_shell', false) === true
441
+ || pick(snapshot, 'workspaceSurface', 'workspace_surface', null) === 'loading_shell'
442
+ : false;
443
+
444
+ if (loadingShell) {
445
+ return {
446
+ ok: false,
447
+ error_code: LOADING_PENDING,
448
+ retryable: true,
449
+ suggested_next_step: 'reverify',
450
+ evidence: summarizeWorkspaceSnapshot(snapshot ?? {}),
451
+ };
452
+ }
453
+
454
+ if (kind === 'draft_action' || expectedText !== undefined) {
455
+ const typeResult = await verifyTypeResult({
456
+ page,
457
+ expectedText: expectedText ?? '',
458
+ allowPageChange,
459
+ prevUrl,
460
+ prevDomRevision,
461
+ newDomRevision,
462
+ });
463
+
464
+ if (typeResult.ok) {
465
+ return typeResult;
466
+ }
467
+
468
+ const composer = getComposer(snapshot);
469
+ const draftText = compactText(composer?.draft_text ?? composer?.draftText ?? '');
470
+ if (composer?.draft_present === true && draftText === compactText(expectedText)) {
471
+ return {
472
+ ok: true,
473
+ evidence: {
474
+ kind,
475
+ target,
476
+ composer_kind: composer.kind ?? null,
477
+ draft_present: true,
478
+ draft_text: draftText,
479
+ summary: snapshot ? pick(snapshot, 'summary', 'summary', null) : null,
480
+ },
481
+ };
482
+ }
483
+
484
+ return typeResult;
485
+ }
486
+
487
+ if (hintId) {
488
+ return verifyGenericAction({
489
+ page,
490
+ hintId,
491
+ prevDomRevision,
492
+ prevUrl,
493
+ prevActiveId,
494
+ newDomRevision,
495
+ });
496
+ }
497
+
498
+ if (outcomeSignals?.delivered || outcomeSignals?.composer_cleared || outcomeSignals?.active_item_stable) {
499
+ return {
500
+ ok: true,
501
+ evidence: {
502
+ kind,
503
+ target,
504
+ outcomeSignals,
505
+ },
506
+ };
507
+ }
508
+
509
+ return {
510
+ ok: false,
511
+ error_code: ACTION_NOT_VERIFIED,
512
+ retryable: true,
513
+ suggested_next_step: 'reverify',
514
+ evidence: {
515
+ kind,
516
+ target,
517
+ outcomeSignals,
518
+ },
519
+ };
520
+ }
521
+
522
+ export async function executeGuardedAction(runtimeOrOptions, execute, verify) {
523
+ const options = runtimeOrOptions && typeof runtimeOrOptions === 'object' && 'runtime' in runtimeOrOptions
524
+ ? runtimeOrOptions
525
+ : {
526
+ runtime: runtimeOrOptions,
527
+ execute,
528
+ verify,
529
+ };
530
+ const runtime = options.runtime;
531
+ const run = options.execute ?? execute;
532
+ const check = options.verify ?? verify;
533
+
534
+ const executionResult = await run();
535
+ const refreshedSnapshot = typeof runtime?.refreshSnapshot === 'function'
536
+ ? await runtime.refreshSnapshot()
537
+ : runtime?.snapshot ?? null;
538
+ const snapshot = normalizeWorkspaceSnapshot(refreshedSnapshot);
539
+
540
+ if (typeof runtime?.persistSnapshot === 'function') {
541
+ await runtime.persistSnapshot(snapshot);
542
+ }
543
+
544
+ if (runtime && typeof runtime === 'object') {
545
+ runtime.snapshot = snapshot;
546
+ }
547
+
548
+ const verification = typeof check === 'function'
549
+ ? await check({ executionResult, snapshot })
550
+ : { ok: true };
551
+
552
+ return {
553
+ ...verification,
554
+ executionResult,
555
+ snapshot,
556
+ };
557
+ }
558
+
559
+ export async function selectItemByHint(runtime, requestedLabel, options = {}) {
560
+ const snapshot = runtime?.snapshot ?? runtime;
561
+ const resolution = resolveLiveItem(snapshot, requestedLabel);
562
+
563
+ if (!resolution.item) {
564
+ return {
565
+ ok: false,
566
+ unresolved: resolution.unresolved,
567
+ snapshot,
568
+ };
569
+ }
570
+
571
+ const item = resolution.item;
572
+ if (!compactText(item?.hint_id)) {
573
+ return {
574
+ ok: false,
575
+ unresolved: buildUnresolved('no_live_target', requestedLabel, [item]),
576
+ snapshot,
577
+ };
578
+ }
579
+
580
+ const page = runtime?.page ?? runtime;
581
+ const click = runtime?.clickByHintId ?? clickByHintId;
582
+ const rebuildHints = runtime?.rebuildHints;
583
+
584
+ return executeGuardedAction(runtime, async () => {
585
+ await click(page, item.hint_id, { rebuildHints });
586
+ return { item };
587
+ }, async ({ snapshot: refreshedSnapshot }) => {
588
+ return verifySelectionResult({
589
+ snapshot: refreshedSnapshot,
590
+ item,
591
+ identitySensitive: resolution.identity_sensitive ?? false,
592
+ });
593
+ });
594
+ }
595
+
596
+ export async function selectWorkspaceItem(runtime, requestedLabel) {
597
+ const state = runtime?.state ?? null;
598
+ if (getWorkspaceStatus(state ?? {}) !== 'direct') {
599
+ const snapshot = normalizeWorkspaceSnapshot(runtime?.snapshot ?? runtime ?? {});
600
+ const details = getSelectionSnapshotDetails(snapshot);
601
+
602
+ return {
603
+ status: 'blocked',
604
+ reason: getWorkspaceStatus(state ?? {}),
605
+ selected_item: null,
606
+ active_item: details.activeItem,
607
+ detail_alignment: details.detailAlignment,
608
+ snapshot,
609
+ selection_evidence: buildSelectionEvidence({
610
+ requestedLabel,
611
+ item: null,
612
+ summary: details.summary,
613
+ activeItem: details.activeItem,
614
+ detailAlignment: details.detailAlignment,
615
+ selectionWindow: details.selectionWindow,
616
+ recoveryHint: 'reinspect_workspace',
617
+ matches: [],
618
+ }),
619
+ };
620
+ }
621
+
622
+ const rawInitialSnapshot = runtime?.snapshot ?? runtime ?? {};
623
+ const initialSnapshot = normalizeWorkspaceSnapshot(rawInitialSnapshot);
624
+ const initialPageUrl = typeof runtime?.page?.url === 'function' ? runtime.page.url() : null;
625
+ const initialDomRevision = state?.pageState?.domRevision ?? null;
626
+ const resolution = resolveWorkspaceSelection(initialSnapshot, requestedLabel);
627
+
628
+ if (!resolution.item) {
629
+ const details = getSelectionSnapshotDetails(initialSnapshot);
630
+ return {
631
+ status: 'unresolved',
632
+ unresolved: resolution.unresolved,
633
+ selected_item: null,
634
+ active_item: details.activeItem,
635
+ detail_alignment: details.detailAlignment,
636
+ snapshot: initialSnapshot,
637
+ selection_evidence: buildSelectionEvidence({
638
+ requestedLabel,
639
+ item: null,
640
+ summary: details.summary,
641
+ activeItem: details.activeItem,
642
+ detailAlignment: details.detailAlignment,
643
+ selectionWindow: details.selectionWindow,
644
+ recoveryHint: resolution.unresolved?.recovery_hint ?? null,
645
+ matches: resolution.matches ?? [],
646
+ }),
647
+ };
648
+ }
649
+
650
+ const item = resolution.item;
651
+ const navigationLike = pick(rawInitialSnapshot, 'workspaceSurface', 'workspace_surface', null) === 'list'
652
+ || state?.pageState?.workspaceSurface === 'list'
653
+ || state?.pageState?.currentRole === 'navigation-heavy';
654
+ if (navigationLike && item.selected === true) {
655
+ const details = getSelectionSnapshotDetails(initialSnapshot);
656
+ return {
657
+ status: 'selected',
658
+ selected_item: item,
659
+ active_item: details.activeItem ?? { label: item.label },
660
+ detail_alignment: details.detailAlignment,
661
+ snapshot: initialSnapshot,
662
+ selection_evidence: buildSelectionEvidence({
663
+ requestedLabel,
664
+ item,
665
+ summary: details.summary,
666
+ activeItem: details.activeItem ?? { label: item.label, selected: true },
667
+ detailAlignment: details.detailAlignment,
668
+ selectionWindow: details.selectionWindow,
669
+ recoveryHint: details.summary.recovery_hint ?? null,
670
+ matches: resolution.matches ?? [item],
671
+ }),
672
+ };
673
+ }
674
+ const selectItem = typeof runtime?.selectItemByHint === 'function'
675
+ ? runtime.selectItemByHint
676
+ : typeof runtime?.clickByHintId === 'function' && compactText(item?.hint_id)
677
+ ? async (candidate) => {
678
+ const page = runtime?.page ?? runtime;
679
+ await runtime.clickByHintId(page, candidate.hint_id, { rebuildHints: runtime?.rebuildHints });
680
+ return { ok: true };
681
+ }
682
+ : null;
683
+
684
+ if (typeof selectItem !== 'function') {
685
+ const details = getSelectionSnapshotDetails(initialSnapshot);
686
+ return {
687
+ status: 'unresolved',
688
+ unresolved: buildSelectionUnresolved('no_live_target', requestedLabel, [item], 'retry_selection'),
689
+ selected_item: item,
690
+ active_item: details.activeItem,
691
+ detail_alignment: details.detailAlignment,
692
+ snapshot: initialSnapshot,
693
+ selection_evidence: buildSelectionEvidence({
694
+ requestedLabel,
695
+ item,
696
+ summary: details.summary,
697
+ activeItem: details.activeItem,
698
+ detailAlignment: details.detailAlignment,
699
+ selectionWindow: details.selectionWindow,
700
+ recoveryHint: 'retry_selection',
701
+ matches: resolution.matches ?? [item],
702
+ }),
703
+ };
704
+ }
705
+
706
+ const executionResult = await selectItem(item);
707
+ if (executionResult && executionResult.ok === false) {
708
+ const details = getSelectionSnapshotDetails(initialSnapshot);
709
+ return {
710
+ status: 'unresolved',
711
+ unresolved: executionResult.unresolved ?? buildSelectionUnresolved('retry_selection', requestedLabel, [item], 'retry_selection'),
712
+ selected_item: item,
713
+ active_item: details.activeItem,
714
+ detail_alignment: details.detailAlignment,
715
+ snapshot: initialSnapshot,
716
+ selection_evidence: buildSelectionEvidence({
717
+ requestedLabel,
718
+ item,
719
+ summary: details.summary,
720
+ activeItem: details.activeItem,
721
+ detailAlignment: details.detailAlignment,
722
+ selectionWindow: details.selectionWindow,
723
+ recoveryHint: executionResult.unresolved?.recovery_hint ?? 'retry_selection',
724
+ matches: resolution.matches ?? [item],
725
+ }),
726
+ };
727
+ }
728
+
729
+ const rawRefreshedSnapshot = typeof runtime?.refreshSnapshot === 'function'
730
+ ? await runtime.refreshSnapshot()
731
+ : runtime?.snapshot ?? runtime ?? {};
732
+ const refreshedSnapshot = normalizeWorkspaceSnapshot(rawRefreshedSnapshot);
733
+ const refreshedPageUrl = typeof runtime?.page?.url === 'function' ? runtime.page.url() : null;
734
+ const refreshedDomRevision = state?.pageState?.domRevision ?? null;
735
+
736
+ if (typeof runtime?.persistSnapshot === 'function') {
737
+ await runtime.persistSnapshot(refreshedSnapshot);
738
+ }
739
+
740
+ if (runtime && typeof runtime === 'object') {
741
+ runtime.snapshot = refreshedSnapshot;
742
+ }
743
+
744
+ const refreshedDetails = getSelectionSnapshotDetails(refreshedSnapshot);
745
+ const refreshedLiveItems = getLiveItems(refreshedSnapshot);
746
+ const normalizedLabel = normalizeLabel(requestedLabel);
747
+ const targetHintId = compactText(item?.hint_id);
748
+ const activeMatch = normalizeLabel(refreshedDetails.activeItem?.label) === normalizedLabel;
749
+ const activeHintMatch = targetHintId && compactText(refreshedDetails.activeItem?.hint_id) === targetHintId;
750
+ const selectedMatch = refreshedLiveItems.some((liveItem) => (
751
+ liveItem?.selected === true
752
+ && getSelectionMatchLabel(liveItem) === normalizedLabel
753
+ ));
754
+ const selectedHintMatch = targetHintId && refreshedLiveItems.some((liveItem) => (
755
+ liveItem?.selected === true
756
+ && compactText(liveItem?.hint_id) === targetHintId
757
+ ));
758
+ const detailAlignment = refreshedDetails.detailAlignment;
759
+ const navigationLikeAfter = pick(rawInitialSnapshot, 'workspaceSurface', 'workspace_surface', null) === 'list'
760
+ || pick(rawRefreshedSnapshot, 'workspaceSurface', 'workspace_surface', null) === 'list'
761
+ || state?.pageState?.workspaceSurface === 'list'
762
+ || state?.pageState?.currentRole === 'navigation-heavy';
763
+ const navigationTransitionDetected = (
764
+ initialPageUrl
765
+ && refreshedPageUrl
766
+ && initialPageUrl !== refreshedPageUrl
767
+ ) || (
768
+ initialDomRevision !== null
769
+ && refreshedDomRevision !== null
770
+ && initialDomRevision !== refreshedDomRevision
771
+ );
772
+ const labelStillVisible = refreshedLiveItems.some((liveItem) => getSelectionMatchLabel(liveItem) === normalizedLabel);
773
+ const navigationConfirmed = navigationLikeAfter
774
+ && detailAlignment !== 'mismatch'
775
+ && (
776
+ selectedMatch
777
+ || activeMatch
778
+ || (navigationTransitionDetected && labelStillVisible && refreshedDetails.selectionWindow !== 'not_found')
779
+ );
780
+ const selectionConfirmed = targetHintId
781
+ ? activeHintMatch || selectedHintMatch || navigationConfirmed
782
+ : activeMatch || selectedMatch || navigationConfirmed;
783
+
784
+ if (detailAlignment === 'mismatch') {
785
+ return {
786
+ status: 'unresolved',
787
+ unresolved: buildSelectionUnresolved('detail_panel_mismatch', requestedLabel, [item], 'reinspect_workspace'),
788
+ selected_item: item,
789
+ active_item: refreshedDetails.activeItem,
790
+ detail_alignment: detailAlignment,
791
+ snapshot: refreshedSnapshot,
792
+ selection_evidence: buildSelectionEvidence({
793
+ requestedLabel,
794
+ item,
795
+ summary: refreshedDetails.summary,
796
+ activeItem: refreshedDetails.activeItem,
797
+ detailAlignment,
798
+ selectionWindow: refreshedDetails.selectionWindow,
799
+ recoveryHint: 'reinspect_workspace',
800
+ matches: resolution.matches ?? [item],
801
+ }),
802
+ };
803
+ }
804
+
805
+ if (selectionConfirmed) {
806
+ return {
807
+ status: 'selected',
808
+ selected_item: item,
809
+ active_item: refreshedDetails.activeItem,
810
+ detail_alignment: detailAlignment,
811
+ snapshot: refreshedSnapshot,
812
+ selection_evidence: buildSelectionEvidence({
813
+ requestedLabel,
814
+ item,
815
+ summary: refreshedDetails.summary,
816
+ activeItem: refreshedDetails.activeItem,
817
+ detailAlignment,
818
+ selectionWindow: refreshedDetails.selectionWindow,
819
+ recoveryHint: refreshedDetails.summary.recovery_hint ?? null,
820
+ matches: resolution.matches ?? [item],
821
+ }),
822
+ };
823
+ }
824
+
825
+ return {
826
+ status: 'unresolved',
827
+ unresolved: buildSelectionUnresolved('virtualized_window_changed', requestedLabel, [item], 'retry_selection'),
828
+ selected_item: item,
829
+ active_item: refreshedDetails.activeItem,
830
+ detail_alignment: detailAlignment,
831
+ snapshot: refreshedSnapshot,
832
+ selection_evidence: buildSelectionEvidence({
833
+ requestedLabel,
834
+ item,
835
+ summary: refreshedDetails.summary,
836
+ activeItem: refreshedDetails.activeItem,
837
+ detailAlignment,
838
+ selectionWindow: refreshedDetails.selectionWindow,
839
+ recoveryHint: 'retry_selection',
840
+ matches: resolution.matches ?? [item],
841
+ }),
842
+ };
843
+ }
844
+
845
+ export async function draftIntoComposer(runtime, text, options = {}) {
846
+ const snapshot = runtime?.snapshot ?? runtime;
847
+ const resolution = resolveComposer(snapshot);
848
+
849
+ if (!resolution.composer) {
850
+ return {
851
+ ok: false,
852
+ unresolved: resolution.unresolved,
853
+ snapshot,
854
+ };
855
+ }
856
+
857
+ const composer = resolution.composer;
858
+ if (!compactText(composer?.hint_id)) {
859
+ return {
860
+ ok: false,
861
+ unresolved: buildUnresolved('no_live_target', 'composer', [composer]),
862
+ snapshot,
863
+ };
864
+ }
865
+
866
+ const page = runtime?.page ?? runtime;
867
+ const type = runtime?.typeByHintId ?? typeByHintId;
868
+ const rebuildHints = runtime?.rebuildHints;
869
+ const prevUrl = typeof page?.url === 'function' ? page.url() : null;
870
+ const prevDomRevision = snapshot?.domRevision ?? 0;
871
+ const pressEnter = false;
872
+
873
+ return executeGuardedAction(runtime, async () => {
874
+ await type(page, composer.hint_id, text, pressEnter, { rebuildHints });
875
+ return { composer, text };
876
+ }, async ({ snapshot: refreshedSnapshot }) => {
877
+ if (typeof page?.evaluate !== 'function' || typeof page?.url !== 'function') {
878
+ return {
879
+ ok: true,
880
+ evidence: createWorkspaceWriteEvidence({ kind: 'draft_action', target: composer.kind ?? 'chat_composer' }),
881
+ };
882
+ }
883
+
884
+ const newDomRevision = refreshedSnapshot?.domRevision ?? prevDomRevision;
885
+
886
+ return verifyActionOutcome({
887
+ page,
888
+ kind: 'draft_action',
889
+ target: composer.kind ?? 'chat_composer',
890
+ expectedText: text,
891
+ allowPageChange: false,
892
+ prevUrl,
893
+ prevDomRevision,
894
+ newDomRevision,
895
+ outcomeSignals: refreshedSnapshot?.outcome_signals ?? null,
896
+ snapshot: refreshedSnapshot,
897
+ });
898
+ });
899
+ }
900
+
901
+ export async function draftWorkspaceAction(runtime, text, options = {}) {
902
+ const state = runtime?.state ?? null;
903
+ const gatewayStatus = getWorkspaceStatus(state ?? {});
904
+ const initialSnapshot = normalizeWorkspaceSnapshot(runtime?.snapshot ?? runtime ?? {});
905
+
906
+ if (gatewayStatus !== 'direct') {
907
+ const composer = resolveComposer(initialSnapshot).composer ?? getComposer(initialSnapshot);
908
+
909
+ return {
910
+ status: 'blocked',
911
+ reason: gatewayStatus,
912
+ draft_present: initialSnapshot?.composer?.draft_present === true,
913
+ snapshot: initialSnapshot,
914
+ draft_evidence: {
915
+ ...createWorkspaceWriteEvidence({ kind: 'draft_action', target: composer?.kind ?? 'chat_composer' }),
916
+ draft_present: initialSnapshot?.composer?.draft_present === true,
917
+ },
918
+ action: {
919
+ kind: 'draft_action',
920
+ status: 'blocked',
921
+ },
922
+ };
923
+ }
924
+
925
+ const resolution = resolveComposer(initialSnapshot);
926
+ if (!resolution.composer) {
927
+ return {
928
+ status: 'unresolved',
929
+ draft_present: initialSnapshot?.composer?.draft_present === true,
930
+ unresolved: resolution.unresolved,
931
+ snapshot: initialSnapshot,
932
+ action: {
933
+ kind: 'draft_action',
934
+ status: 'unresolved',
935
+ },
936
+ };
937
+ }
938
+
939
+ const composer = resolution.composer;
940
+ if (!compactText(composer?.hint_id)) {
941
+ return {
942
+ status: 'unresolved',
943
+ draft_present: initialSnapshot?.composer?.draft_present === true,
944
+ unresolved: buildUnresolved('no_live_target', 'composer', [composer]),
945
+ snapshot: initialSnapshot,
946
+ action: {
947
+ kind: 'draft_action',
948
+ status: 'unresolved',
949
+ },
950
+ };
951
+ }
952
+
953
+ const draft = typeof runtime?.draftIntoComposer === 'function'
954
+ ? runtime.draftIntoComposer
955
+ : draftIntoComposer;
956
+
957
+ const draftResult = await draft(runtime, text, options);
958
+ const resultSnapshot = normalizeWorkspaceSnapshot(draftResult?.snapshot ?? runtime?.snapshot ?? initialSnapshot);
959
+
960
+ if (runtime && typeof runtime === 'object') {
961
+ runtime.snapshot = resultSnapshot;
962
+ }
963
+
964
+ if (!draftResult?.ok) {
965
+ const resultStatus = draftResult?.unresolved ? 'unresolved' : 'failed';
966
+
967
+ return {
968
+ status: resultStatus,
969
+ draft_present: resultSnapshot?.composer?.draft_present === true,
970
+ unresolved: draftResult?.unresolved ?? null,
971
+ error_code: draftResult?.error_code ?? null,
972
+ retryable: draftResult?.retryable ?? null,
973
+ suggested_next_step: draftResult?.suggested_next_step ?? null,
974
+ snapshot: resultSnapshot,
975
+ action: {
976
+ kind: 'draft_action',
977
+ status: resultStatus,
978
+ },
979
+ };
980
+ }
981
+
982
+ return {
983
+ status: 'drafted',
984
+ draft_present: resultSnapshot?.composer?.draft_present === true,
985
+ snapshot: resultSnapshot,
986
+ draft_evidence: {
987
+ ...createWorkspaceWriteEvidence({ kind: 'draft_action', target: composer.kind ?? 'chat_composer' }),
988
+ draft_present: resultSnapshot?.composer?.draft_present === true,
989
+ summary: resultSnapshot?.summary?.summary ?? resultSnapshot?.summary_text ?? null,
990
+ },
991
+ action: {
992
+ kind: 'draft_action',
993
+ status: 'drafted',
994
+ },
995
+ };
996
+ }
997
+
998
+ export async function executeWorkspaceAction(runtime, options = {}) {
999
+ const state = runtime?.state ?? null;
1000
+ const gatewayStatus = getWorkspaceStatus(state ?? {});
1001
+ const initialSnapshot = normalizeWorkspaceSnapshot(runtime?.snapshot ?? runtime ?? {});
1002
+ const action = options?.action ?? 'send';
1003
+ const mode = options?.mode ?? 'preview';
1004
+ const confirmation = options?.confirmation;
1005
+
1006
+ if (gatewayStatus !== 'direct') {
1007
+ return {
1008
+ status: 'blocked',
1009
+ blocked: true,
1010
+ executed: false,
1011
+ reason: gatewayStatus,
1012
+ unresolved: null,
1013
+ failure: null,
1014
+ action: {
1015
+ kind: 'execute_action',
1016
+ status: 'blocked',
1017
+ },
1018
+ snapshot: initialSnapshot,
1019
+ workspace: null,
1020
+ summary: null,
1021
+ };
1022
+ }
1023
+
1024
+ if (action !== 'send') {
1025
+ return buildWorkspaceExecuteUnresolved('unsupported_action', action, [], null, initialSnapshot);
1026
+ }
1027
+
1028
+ const workspaceExecuteSignals = getWorkspaceExecuteSignals(initialSnapshot);
1029
+ if (!workspaceExecuteSignals.sendControl || !compactText(workspaceExecuteSignals.sendControl?.hint_id)) {
1030
+ return buildWorkspaceExecuteUnresolved('no_live_target', 'send', workspaceExecuteSignals.sendControl ? [workspaceExecuteSignals.sendControl] : [], 'reinspect_workspace', initialSnapshot);
1031
+ }
1032
+
1033
+ if (!canExecuteWorkspaceSend(initialSnapshot)) {
1034
+ return buildWorkspaceExecuteBlocked('not_ready_to_execute', 'send', initialSnapshot);
1035
+ }
1036
+
1037
+ if (mode === 'preview') {
1038
+ return buildWorkspaceExecuteBlocked('preview_safe', 'send', initialSnapshot);
1039
+ }
1040
+
1041
+ if (confirmation !== 'EXECUTE') {
1042
+ return buildWorkspaceExecuteBlocked('confirmation_required', 'send', initialSnapshot);
1043
+ }
1044
+
1045
+ const page = runtime?.page ?? runtime;
1046
+ const click = runtime?.clickByHintId ?? clickByHintId;
1047
+ const rebuildHints = runtime?.rebuildHints;
1048
+ const execute = runtime?.executeGuardedAction ?? executeGuardedAction;
1049
+ const verify = runtime?.verifyActionOutcome ?? verifyActionOutcome;
1050
+
1051
+ const execution = await execute({
1052
+ runtime,
1053
+ execute: async () => {
1054
+ await click(page, workspaceExecuteSignals.sendControl.hint_id, { rebuildHints });
1055
+ return { control: workspaceExecuteSignals.sendControl };
1056
+ },
1057
+ verify: async ({ snapshot: refreshedSnapshot, executionResult }) => {
1058
+ const verification = await verify({
1059
+ page,
1060
+ kind: 'execute_action',
1061
+ target: 'send',
1062
+ outcomeSignals: refreshedSnapshot?.outcome_signals ?? null,
1063
+ snapshot: refreshedSnapshot,
1064
+ });
1065
+ const sendDelivered = refreshedSnapshot?.outcome_signals?.delivered === true;
1066
+ const composerCleared = refreshedSnapshot?.outcome_signals?.composer_cleared === true;
1067
+ const sendSucceeded = sendDelivered || composerCleared;
1068
+
1069
+ if (!sendSucceeded) {
1070
+ return {
1071
+ ok: false,
1072
+ failure: {
1073
+ error_code: verification?.error_code ?? 'ACTION_NOT_VERIFIED',
1074
+ retryable: verification?.retryable ?? true,
1075
+ suggested_next_step: verification?.suggested_next_step ?? 'reverify',
1076
+ },
1077
+ evidence: verification?.evidence ?? {
1078
+ kind: 'execute_action',
1079
+ target: 'send',
1080
+ executionResult,
1081
+ },
1082
+ };
1083
+ }
1084
+
1085
+ return {
1086
+ ok: true,
1087
+ evidence: verification?.evidence ?? {
1088
+ kind: 'execute_action',
1089
+ target: 'send',
1090
+ executionResult,
1091
+ },
1092
+ verification: {
1093
+ delivered: sendDelivered,
1094
+ composer_cleared: composerCleared,
1095
+ active_item_stable: refreshedSnapshot?.outcome_signals?.active_item_stable === true,
1096
+ },
1097
+ };
1098
+ },
1099
+ });
1100
+
1101
+ const resultSnapshot = normalizeWorkspaceSnapshot(execution?.snapshot ?? runtime?.snapshot ?? initialSnapshot);
1102
+
1103
+ if (runtime && typeof runtime === 'object') {
1104
+ runtime.snapshot = resultSnapshot;
1105
+ }
1106
+
1107
+ if (!execution?.ok) {
1108
+ return buildWorkspaceExecuteFailed(
1109
+ execution?.failure ?? {
1110
+ error_code: execution?.error_code ?? 'ACTION_NOT_VERIFIED',
1111
+ retryable: execution?.retryable ?? true,
1112
+ suggested_next_step: execution?.suggested_next_step ?? 'reverify',
1113
+ },
1114
+ resultSnapshot,
1115
+ );
1116
+ }
1117
+
1118
+ return {
1119
+ status: 'success',
1120
+ blocked: false,
1121
+ executed: true,
1122
+ reason: null,
1123
+ unresolved: null,
1124
+ failure: null,
1125
+ verification: execution.verification ?? {
1126
+ delivered: resultSnapshot?.outcome_signals?.delivered === true,
1127
+ composer_cleared: resultSnapshot?.outcome_signals?.composer_cleared === true,
1128
+ active_item_stable: resultSnapshot?.outcome_signals?.active_item_stable === true,
1129
+ },
1130
+ action: {
1131
+ kind: 'execute_action',
1132
+ status: 'executed',
1133
+ },
1134
+ snapshot: resultSnapshot,
1135
+ workspace: null,
1136
+ summary: resultSnapshot?.summary?.summary ?? resultSnapshot?.summary_text ?? null,
1137
+ };
1138
+ }