@geometra/mcp 1.63.0 → 1.64.0

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.
@@ -18,16 +18,59 @@ function resolveSessionStateFile() {
18
18
  mkdirSync(dir, { recursive: true });
19
19
  return path.join(dir, `parallel-mcp-${process.pid}-${threadId}.sqlite`);
20
20
  }
21
- const orchestrator = new ParallelMcpOrchestrator(new SqliteParallelMcpStore({ filename: resolveSessionStateFile() }), { defaultLeaseMs: SESSION_LEASE_MS });
22
- const leaseSweep = setInterval(() => {
21
+ function isSessionLifecycleDisabled() {
22
+ const raw = process.env.GEOMETRA_MCP_DISABLE_SESSION_LIFECYCLE?.trim().toLowerCase();
23
+ return raw === '1' || raw === 'true' || raw === 'yes' || raw === 'on';
24
+ }
25
+ function formatSessionLifecycleInitError(error) {
26
+ if (error instanceof Error) {
27
+ if (error.code === 'ERR_DLOPEN_FAILED' &&
28
+ error.message.includes('better_sqlite3.node')) {
29
+ return `${error.message}. Rebuild the native module with \`npm rebuild better-sqlite3\` in the MCP package directory, or reinstall dependencies for the current Node.js version.`;
30
+ }
31
+ return error.message;
32
+ }
33
+ return String(error);
34
+ }
35
+ function createSessionLifecycleRegistry() {
36
+ if (isSessionLifecycleDisabled()) {
37
+ process.stderr.write('[geometra-mcp] durable session lifecycle disabled via GEOMETRA_MCP_DISABLE_SESSION_LIFECYCLE\n');
38
+ return {
39
+ available: false,
40
+ orchestrator: null,
41
+ close: () => { },
42
+ };
43
+ }
23
44
  try {
24
- orchestrator.expireLeases();
45
+ const orchestrator = new ParallelMcpOrchestrator(new SqliteParallelMcpStore({ filename: resolveSessionStateFile() }), { defaultLeaseMs: SESSION_LEASE_MS });
46
+ const leaseSweep = setInterval(() => {
47
+ try {
48
+ orchestrator.expireLeases();
49
+ }
50
+ catch {
51
+ /* ignore background lease sweep failures */
52
+ }
53
+ }, SESSION_SWEEP_MS);
54
+ leaseSweep.unref();
55
+ return {
56
+ available: true,
57
+ orchestrator,
58
+ close: () => {
59
+ clearInterval(leaseSweep);
60
+ orchestrator.close();
61
+ },
62
+ };
25
63
  }
26
- catch {
27
- /* ignore background lease sweep failures */
64
+ catch (error) {
65
+ process.stderr.write(`[geometra-mcp] durable session lifecycle disabled: ${formatSessionLifecycleInitError(error)}\n`);
66
+ return {
67
+ available: false,
68
+ orchestrator: null,
69
+ close: () => { },
70
+ };
28
71
  }
29
- }, SESSION_SWEEP_MS);
30
- leaseSweep.unref();
72
+ }
73
+ const lifecycleRegistry = createSessionLifecycleRegistry();
31
74
  function extractPageUrl(target) {
32
75
  const cached = target.cachedA11y?.meta?.pageUrl;
33
76
  if (typeof cached === 'string' && cached.length > 0)
@@ -86,6 +129,15 @@ function workerIdFor(sessionId) {
86
129
  return `${SESSION_WORKER_PREFIX}:${sessionId}`;
87
130
  }
88
131
  export function initializeSessionLifecycle(target, options) {
132
+ if (!lifecycleRegistry.available || !lifecycleRegistry.orchestrator) {
133
+ target.lifecycleFinalized = true;
134
+ target.lifecycleTaskId = undefined;
135
+ target.lifecycleTaskKind = undefined;
136
+ target.lifecycleLeaseId = undefined;
137
+ target.lifecycleWorkerId = undefined;
138
+ return;
139
+ }
140
+ const orchestrator = lifecycleRegistry.orchestrator;
89
141
  const sessionId = target.id;
90
142
  const taskId = liveTaskIdFor(sessionId);
91
143
  const taskKind = liveTaskKindFor(sessionId);
@@ -131,6 +183,9 @@ export function initializeSessionLifecycle(target, options) {
131
183
  target.lifecycleFinalized = false;
132
184
  }
133
185
  export function heartbeatSessionLifecycle(target) {
186
+ if (!lifecycleRegistry.orchestrator)
187
+ return;
188
+ const orchestrator = lifecycleRegistry.orchestrator;
134
189
  if (target.lifecycleFinalized || !target.lifecycleTaskId || !target.lifecycleLeaseId || !target.lifecycleWorkerId)
135
190
  return;
136
191
  orchestrator.heartbeatLease({
@@ -141,6 +196,9 @@ export function heartbeatSessionLifecycle(target) {
141
196
  });
142
197
  }
143
198
  export function recordSessionSnapshot(target, label, extra) {
199
+ if (!lifecycleRegistry.orchestrator)
200
+ return;
201
+ const orchestrator = lifecycleRegistry.orchestrator;
144
202
  if (!target.lifecycleTaskId)
145
203
  return;
146
204
  orchestrator.appendContextSnapshot({
@@ -152,6 +210,9 @@ export function recordSessionSnapshot(target, label, extra) {
152
210
  });
153
211
  }
154
212
  export function completeSessionLifecycle(target, reason, extra) {
213
+ if (!lifecycleRegistry.orchestrator)
214
+ return;
215
+ const orchestrator = lifecycleRegistry.orchestrator;
155
216
  if (target.lifecycleFinalized || !target.lifecycleTaskId || !target.lifecycleLeaseId || !target.lifecycleWorkerId)
156
217
  return;
157
218
  orchestrator.completeTask({
@@ -171,6 +232,9 @@ export function completeSessionLifecycle(target, reason, extra) {
171
232
  target.lifecycleFinalized = true;
172
233
  }
173
234
  export function failSessionLifecycle(target, error, extra) {
235
+ if (!lifecycleRegistry.orchestrator)
236
+ return;
237
+ const orchestrator = lifecycleRegistry.orchestrator;
174
238
  if (target.lifecycleFinalized || !target.lifecycleTaskId || !target.lifecycleLeaseId || !target.lifecycleWorkerId)
175
239
  return;
176
240
  recordSessionSnapshot(target, 'session.failed', {
@@ -187,6 +251,5 @@ export function failSessionLifecycle(target, error, extra) {
187
251
  target.lifecycleFinalized = true;
188
252
  }
189
253
  export function shutdownSessionLifecycleRegistry() {
190
- clearInterval(leaseSweep);
191
- orchestrator.close();
254
+ lifecycleRegistry.close();
192
255
  }
package/dist/session.d.ts CHANGED
@@ -29,6 +29,17 @@ export interface A11yNode {
29
29
  scrollX?: number;
30
30
  scrollY?: number;
31
31
  controlTag?: string;
32
+ /** Stable authored DOM identity emitted by the proxy extractor (`id:` or `name:`). */
33
+ controlKey?: string;
34
+ controlId?: string;
35
+ controlName?: string;
36
+ options?: Array<{
37
+ value: string;
38
+ label: string;
39
+ disabled: boolean;
40
+ selected: boolean;
41
+ index: number;
42
+ }>;
32
43
  placeholder?: string;
33
44
  inputPattern?: string;
34
45
  inputType?: string;
@@ -284,8 +295,19 @@ export interface PageSectionDetail {
284
295
  export type FormSchemaFieldKind = 'text' | 'choice' | 'toggle' | 'multi_choice';
285
296
  export type FormSchemaChoiceType = 'select' | 'group' | 'listbox';
286
297
  export type FormSchemaContextMode = 'auto' | 'always' | 'none';
298
+ export interface FormSchemaOption {
299
+ /** Stable within the authored control identity; index disambiguates duplicate values/labels. */
300
+ id: string;
301
+ value: string;
302
+ label: string;
303
+ index: number;
304
+ disabled?: boolean;
305
+ selected?: boolean;
306
+ }
287
307
  export interface FormSchemaField {
288
308
  id: string;
309
+ /** Authored DOM identity used for exact proxy-side resolution before accessible-label fallback. */
310
+ fieldKey?: string;
289
311
  kind: FormSchemaFieldKind;
290
312
  label: string;
291
313
  required?: boolean;
@@ -299,6 +321,7 @@ export interface FormSchemaField {
299
321
  values?: string[];
300
322
  optionCount?: number;
301
323
  options?: string[];
324
+ optionDetails?: FormSchemaOption[];
302
325
  aliases?: Record<string, string[]>;
303
326
  format?: {
304
327
  placeholder?: string;
@@ -346,6 +369,60 @@ export interface FormSchemaBuildOptions {
346
369
  includeOptions?: boolean;
347
370
  includeContext?: FormSchemaContextMode;
348
371
  }
372
+ export interface FormGraphSource {
373
+ id: string;
374
+ kind: 'html';
375
+ title?: string;
376
+ url?: string;
377
+ }
378
+ export interface FormGraphSourceAnchor {
379
+ sourceId: string;
380
+ kind: 'html';
381
+ fieldName?: string;
382
+ pointer?: string;
383
+ }
384
+ export interface FormGraphField {
385
+ id: string;
386
+ path: string;
387
+ label: string;
388
+ kind: 'boolean' | 'enum' | 'text' | 'textarea' | 'email' | 'phone' | 'date' | 'number';
389
+ required?: boolean;
390
+ reviewRequired?: boolean;
391
+ aliases?: string[];
392
+ options?: Array<{
393
+ value: string;
394
+ label: string;
395
+ }>;
396
+ constraints?: {
397
+ pattern?: string;
398
+ };
399
+ sourceAnchors?: FormGraphSourceAnchor[];
400
+ metadata?: Record<string, unknown>;
401
+ }
402
+ export interface FormGraphModel {
403
+ formgraph: '0.1';
404
+ id: string;
405
+ title: string;
406
+ description?: string;
407
+ sources: FormGraphSource[];
408
+ fields: FormGraphField[];
409
+ evidence: [];
410
+ dependencies: [];
411
+ review: {
412
+ autoSubmitAllowed: false;
413
+ requiredBeforeSubmit: true;
414
+ };
415
+ metadata: {
416
+ producer: 'geometra';
417
+ geometra: {
418
+ formId: string;
419
+ fieldCount: number;
420
+ requiredCount: number;
421
+ invalidCount: number;
422
+ sections?: FormSchemaSection[];
423
+ };
424
+ };
425
+ }
349
426
  export interface UiNodeUpdate {
350
427
  before: CompactUiNode;
351
428
  after: CompactUiNode;
@@ -460,12 +537,14 @@ export interface UpdateWaitResult {
460
537
  export type ProxyFillField = {
461
538
  kind: 'auto';
462
539
  fieldId?: string;
540
+ fieldKey?: string;
463
541
  fieldLabel: string;
464
542
  value: string | boolean;
465
543
  exact?: boolean;
466
544
  } | {
467
545
  kind: 'text';
468
546
  fieldId?: string;
547
+ fieldKey?: string;
469
548
  fieldLabel: string;
470
549
  value: string;
471
550
  exact?: boolean;
@@ -474,13 +553,17 @@ export type ProxyFillField = {
474
553
  } | {
475
554
  kind: 'choice';
476
555
  fieldId?: string;
556
+ fieldKey?: string;
477
557
  fieldLabel: string;
478
558
  value: string;
559
+ optionIndex?: number;
479
560
  query?: string;
480
561
  exact?: boolean;
481
562
  choiceType?: FormSchemaChoiceType;
482
563
  } | {
483
564
  kind: 'toggle';
565
+ fieldId?: string;
566
+ fieldKey?: string;
484
567
  label: string;
485
568
  checked?: boolean;
486
569
  exact?: boolean;
@@ -488,6 +571,7 @@ export type ProxyFillField = {
488
571
  } | {
489
572
  kind: 'file';
490
573
  fieldId?: string;
574
+ fieldKey?: string;
491
575
  fieldLabel: string;
492
576
  paths: string[];
493
577
  exact?: boolean;
@@ -625,6 +709,8 @@ export declare function sendFileUpload(session: Session, paths: string[], opts?:
625
709
  x: number;
626
710
  y: number;
627
711
  };
712
+ fieldId?: string;
713
+ fieldKey?: string;
628
714
  fieldLabel?: string;
629
715
  exact?: boolean;
630
716
  strategy?: 'auto' | 'chooser' | 'hidden' | 'drop';
@@ -637,6 +723,7 @@ export declare function sendFileUpload(session: Session, paths: string[], opts?:
637
723
  export declare function sendFieldText(session: Session, fieldLabel: string, value: string, opts?: {
638
724
  exact?: boolean;
639
725
  fieldId?: string;
726
+ fieldKey?: string;
640
727
  typingDelayMs?: number;
641
728
  imeFriendly?: boolean;
642
729
  }, timeoutMs?: number): Promise<UpdateWaitResult>;
@@ -646,6 +733,8 @@ export declare function sendFieldChoice(session: Session, fieldLabel: string, va
646
733
  query?: string;
647
734
  choiceType?: FormSchemaChoiceType;
648
735
  fieldId?: string;
736
+ fieldKey?: string;
737
+ optionIndex?: number;
649
738
  }, timeoutMs?: number): Promise<UpdateWaitResult>;
650
739
  /** Fill several semantic form fields in one proxy-side batch. */
651
740
  export declare function sendFillFields(session: Session, fields: ProxyFillField[], timeoutMs?: number): Promise<UpdateWaitResult>;
@@ -668,6 +757,8 @@ export declare function sendListboxPick(session: Session, label: string, opts?:
668
757
  };
669
758
  fieldLabel?: string;
670
759
  query?: string;
760
+ fieldId?: string;
761
+ fieldKey?: string;
671
762
  }, timeoutMs?: number): Promise<UpdateWaitResult>;
672
763
  /** Native `<select>` only: click the control center, then pick by value, label text, or zero-based index. */
673
764
  export declare function sendSelectOption(session: Session, x: number, y: number, option: {
@@ -680,6 +771,9 @@ export declare function sendSetChecked(session: Session, label: string, opts?: {
680
771
  checked?: boolean;
681
772
  exact?: boolean;
682
773
  controlType?: 'checkbox' | 'radio';
774
+ fieldKey?: string;
775
+ contextText?: string;
776
+ sectionText?: string;
683
777
  }, timeoutMs?: number): Promise<UpdateWaitResult>;
684
778
  /** Mouse wheel / scroll. Optional `x`,`y` move pointer before scrolling. */
685
779
  export declare function sendWheel(session: Session, deltaY: number, opts?: {
@@ -732,6 +826,8 @@ export declare function buildPageModel(root: A11yNode, options?: {
732
826
  blockDetection?: boolean;
733
827
  }): PageModel;
734
828
  export declare function buildFormSchemas(root: A11yNode, options?: FormSchemaBuildOptions): FormSchemaModel[];
829
+ export declare function buildFormGraphs(root: A11yNode, options?: FormSchemaBuildOptions): FormGraphModel[];
830
+ export declare function formSchemaToFormGraph(schema: FormSchemaModel, pageUrl?: string): FormGraphModel;
735
831
  /**
736
832
  * Required-field snapshot for automation: every required field in a form, including
737
833
  * offscreen entries, annotated with visibility and scroll hints so agents do not