@pooder/core 2.0.0 → 2.1.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.
package/dist/index.mjs CHANGED
@@ -245,6 +245,11 @@ var ExtensionManager = class {
245
245
  const commandService = this.context.services.get("CommandService");
246
246
  return commandService.registerCommand(item.id, item.data.handler);
247
247
  }
248
+ if (pointId === ContributionPointIds.TOOLS) {
249
+ const toolRegistry = this.context.services.get("ToolRegistryService");
250
+ if (!toolRegistry) return;
251
+ return toolRegistry.registerTool(item.data);
252
+ }
248
253
  }
249
254
  unregister(name) {
250
255
  const extension = this.extensionRegistry.get(name);
@@ -447,27 +452,286 @@ var ConfigurationService = class {
447
452
  }
448
453
  };
449
454
 
455
+ // src/services/ToolRegistryService.ts
456
+ var ToolRegistryService = class {
457
+ constructor() {
458
+ this.tools = /* @__PURE__ */ new Map();
459
+ }
460
+ registerTool(tool) {
461
+ if (!(tool == null ? void 0 : tool.id)) {
462
+ throw new Error("ToolContribution.id is required.");
463
+ }
464
+ this.tools.set(tool.id, tool);
465
+ return {
466
+ dispose: () => {
467
+ if (this.tools.get(tool.id) === tool) {
468
+ this.tools.delete(tool.id);
469
+ }
470
+ }
471
+ };
472
+ }
473
+ unregisterTool(toolId) {
474
+ this.tools.delete(toolId);
475
+ }
476
+ getTool(toolId) {
477
+ return this.tools.get(toolId);
478
+ }
479
+ listTools() {
480
+ return Array.from(this.tools.values());
481
+ }
482
+ hasTool(toolId) {
483
+ return this.tools.has(toolId);
484
+ }
485
+ dispose() {
486
+ this.tools.clear();
487
+ }
488
+ };
489
+
490
+ // src/services/ToolSessionService.ts
491
+ var ToolSessionService = class {
492
+ constructor() {
493
+ this.sessions = /* @__PURE__ */ new Map();
494
+ this.dirtyTrackers = /* @__PURE__ */ new Map();
495
+ }
496
+ setCommandService(commandService) {
497
+ this.commandService = commandService;
498
+ }
499
+ setToolRegistry(toolRegistry) {
500
+ this.toolRegistry = toolRegistry;
501
+ }
502
+ registerDirtyTracker(toolId, callback) {
503
+ const wrapped = () => {
504
+ try {
505
+ return callback();
506
+ } catch (e) {
507
+ return false;
508
+ }
509
+ };
510
+ this.dirtyTrackers.set(toolId, wrapped);
511
+ return {
512
+ dispose: () => {
513
+ if (this.dirtyTrackers.get(toolId) === wrapped) {
514
+ this.dirtyTrackers.delete(toolId);
515
+ }
516
+ }
517
+ };
518
+ }
519
+ ensureSession(toolId) {
520
+ const existing = this.sessions.get(toolId);
521
+ if (existing) return existing;
522
+ const created = {
523
+ toolId,
524
+ status: "idle",
525
+ dirty: false
526
+ };
527
+ this.sessions.set(toolId, created);
528
+ return created;
529
+ }
530
+ getState(toolId) {
531
+ return { ...this.ensureSession(toolId) };
532
+ }
533
+ isDirty(toolId) {
534
+ const tracker = this.dirtyTrackers.get(toolId);
535
+ if (tracker) return tracker();
536
+ return this.ensureSession(toolId).dirty;
537
+ }
538
+ markDirty(toolId, dirty = true) {
539
+ const session = this.ensureSession(toolId);
540
+ session.dirty = dirty;
541
+ session.lastUpdatedAt = Date.now();
542
+ }
543
+ resolveTool(toolId) {
544
+ var _a;
545
+ return (_a = this.toolRegistry) == null ? void 0 : _a.getTool(toolId);
546
+ }
547
+ async runCommand(commandId, ...args) {
548
+ if (!commandId || !this.commandService) return void 0;
549
+ return await this.commandService.executeCommand(commandId, ...args);
550
+ }
551
+ async begin(toolId) {
552
+ var _a;
553
+ const tool = this.resolveTool(toolId);
554
+ const session = this.ensureSession(toolId);
555
+ if (session.status === "active") return;
556
+ await this.runCommand((_a = tool == null ? void 0 : tool.commands) == null ? void 0 : _a.begin);
557
+ session.status = "active";
558
+ session.startedAt = Date.now();
559
+ session.lastUpdatedAt = session.startedAt;
560
+ }
561
+ async validate(toolId) {
562
+ var _a;
563
+ const tool = this.resolveTool(toolId);
564
+ if (!((_a = tool == null ? void 0 : tool.commands) == null ? void 0 : _a.validate)) {
565
+ return { ok: true };
566
+ }
567
+ const result = await this.runCommand(tool.commands.validate);
568
+ if (result === false) return { ok: false, result };
569
+ if (result && typeof result === "object" && "ok" in result) {
570
+ return { ok: Boolean(result.ok), result };
571
+ }
572
+ return { ok: true, result };
573
+ }
574
+ async commit(toolId) {
575
+ var _a;
576
+ const tool = this.resolveTool(toolId);
577
+ const validateResult = await this.validate(toolId);
578
+ if (!validateResult.ok) return validateResult;
579
+ const result = await this.runCommand((_a = tool == null ? void 0 : tool.commands) == null ? void 0 : _a.commit);
580
+ const session = this.ensureSession(toolId);
581
+ session.dirty = false;
582
+ session.status = "idle";
583
+ session.lastUpdatedAt = Date.now();
584
+ return { ok: true, result };
585
+ }
586
+ async rollback(toolId) {
587
+ var _a, _b;
588
+ const tool = this.resolveTool(toolId);
589
+ await this.runCommand(((_a = tool == null ? void 0 : tool.commands) == null ? void 0 : _a.rollback) || ((_b = tool == null ? void 0 : tool.commands) == null ? void 0 : _b.reset));
590
+ const session = this.ensureSession(toolId);
591
+ session.dirty = false;
592
+ session.status = "idle";
593
+ session.lastUpdatedAt = Date.now();
594
+ }
595
+ deactivateSession(toolId) {
596
+ const session = this.ensureSession(toolId);
597
+ session.status = "idle";
598
+ session.lastUpdatedAt = Date.now();
599
+ }
600
+ async handleBeforeLeave(toolId) {
601
+ var _a, _b;
602
+ const tool = this.resolveTool(toolId);
603
+ if (!tool) return { decision: "allow" };
604
+ if (tool.interaction !== "session") return { decision: "allow" };
605
+ const dirty = this.isDirty(toolId);
606
+ if (!dirty) return { decision: "allow" };
607
+ const leavePolicy = (_b = (_a = tool.session) == null ? void 0 : _a.leavePolicy) != null ? _b : "block";
608
+ if (leavePolicy === "commit") {
609
+ const committed = await this.commit(toolId);
610
+ if (!committed.ok) {
611
+ return { decision: "blocked", reason: "session-validation-failed" };
612
+ }
613
+ return { decision: "allow" };
614
+ }
615
+ if (leavePolicy === "rollback") {
616
+ await this.rollback(toolId);
617
+ return { decision: "allow" };
618
+ }
619
+ return { decision: "blocked", reason: "session-dirty" };
620
+ }
621
+ dispose() {
622
+ this.sessions.clear();
623
+ this.dirtyTrackers.clear();
624
+ }
625
+ };
626
+
450
627
  // src/services/WorkbenchService.ts
451
628
  var WorkbenchService = class {
452
629
  constructor() {
453
630
  this._activeToolId = null;
631
+ this.guards = [];
454
632
  }
455
633
  init() {
456
634
  }
457
635
  dispose() {
636
+ this.guards = [];
458
637
  }
459
638
  setEventBus(bus) {
460
639
  this.eventBus = bus;
461
640
  }
641
+ setToolRegistry(toolRegistry) {
642
+ this.toolRegistry = toolRegistry;
643
+ }
644
+ setToolSessionService(sessionService) {
645
+ this.sessionService = sessionService;
646
+ }
462
647
  get activeToolId() {
463
648
  return this._activeToolId;
464
649
  }
465
- activate(id) {
466
- var _a;
467
- if (this._activeToolId === id) return;
650
+ registerSwitchGuard(guard, priority = 0) {
651
+ const item = { guard, priority };
652
+ this.guards.push(item);
653
+ this.guards.sort((a, b) => b.priority - a.priority);
654
+ return {
655
+ dispose: () => {
656
+ const index = this.guards.indexOf(item);
657
+ if (index >= 0) this.guards.splice(index, 1);
658
+ }
659
+ };
660
+ }
661
+ async runGuards(context) {
662
+ for (const { guard } of this.guards) {
663
+ const allowed = await Promise.resolve(guard(context));
664
+ if (!allowed) return false;
665
+ }
666
+ return true;
667
+ }
668
+ async switchTool(id, options) {
669
+ var _a, _b, _c, _d, _e;
670
+ if (this._activeToolId === id) {
671
+ return { ok: true, from: this._activeToolId, to: id };
672
+ }
673
+ if (id && this.toolRegistry && !this.toolRegistry.hasTool(id)) {
674
+ return {
675
+ ok: false,
676
+ from: this._activeToolId,
677
+ to: id,
678
+ reason: `tool-not-registered:${id}`
679
+ };
680
+ }
681
+ const context = {
682
+ from: this._activeToolId,
683
+ to: id,
684
+ reason: options == null ? void 0 : options.reason
685
+ };
686
+ const guardAllowed = await this.runGuards(context);
687
+ if (!guardAllowed) {
688
+ (_a = this.eventBus) == null ? void 0 : _a.emit("tool:switch:blocked", {
689
+ ...context,
690
+ reason: "blocked-by-guard"
691
+ });
692
+ return {
693
+ ok: false,
694
+ from: this._activeToolId,
695
+ to: id,
696
+ reason: "blocked-by-guard"
697
+ };
698
+ }
699
+ if (context.from && this.sessionService) {
700
+ const leaveResult = await this.sessionService.handleBeforeLeave(
701
+ context.from
702
+ );
703
+ if (leaveResult.decision === "blocked") {
704
+ (_b = this.eventBus) == null ? void 0 : _b.emit("tool:switch:blocked", {
705
+ ...context,
706
+ reason: leaveResult.reason || "session-blocked"
707
+ });
708
+ return {
709
+ ok: false,
710
+ from: this._activeToolId,
711
+ to: id,
712
+ reason: leaveResult.reason || "session-blocked"
713
+ };
714
+ }
715
+ this.sessionService.deactivateSession(context.from);
716
+ }
717
+ if (id && this.sessionService && this.toolRegistry) {
718
+ const tool = this.toolRegistry.getTool(id);
719
+ if ((tool == null ? void 0 : tool.interaction) === "session" && ((_c = tool.session) == null ? void 0 : _c.autoBegin) !== false) {
720
+ await this.sessionService.begin(id);
721
+ }
722
+ }
468
723
  const previous = this._activeToolId;
469
724
  this._activeToolId = id;
470
- (_a = this.eventBus) == null ? void 0 : _a.emit("tool:activated", { id, previous });
725
+ const reason = options == null ? void 0 : options.reason;
726
+ (_d = this.eventBus) == null ? void 0 : _d.emit("tool:activated", { id, previous, reason });
727
+ (_e = this.eventBus) == null ? void 0 : _e.emit("tool:switch", { from: previous, to: id, reason });
728
+ return { ok: true, from: previous, to: id };
729
+ }
730
+ async activate(id) {
731
+ return await this.switchTool(id, { reason: "activate" });
732
+ }
733
+ async deactivate() {
734
+ return await this.switchTool(null, { reason: "deactivate" });
471
735
  }
472
736
  };
473
737
 
@@ -482,8 +746,16 @@ var Pooder = class {
482
746
  this.registerService(commandService, "CommandService");
483
747
  const configurationService = new ConfigurationService();
484
748
  this.registerService(configurationService, "ConfigurationService");
749
+ const toolRegistryService = new ToolRegistryService();
750
+ this.registerService(toolRegistryService, "ToolRegistryService");
751
+ const toolSessionService = new ToolSessionService();
752
+ toolSessionService.setCommandService(commandService);
753
+ toolSessionService.setToolRegistry(toolRegistryService);
754
+ this.registerService(toolSessionService, "ToolSessionService");
485
755
  const workbenchService = new WorkbenchService();
486
756
  workbenchService.setEventBus(this.eventBus);
757
+ workbenchService.setToolRegistry(toolRegistryService);
758
+ workbenchService.setToolSessionService(toolSessionService);
487
759
  this.registerService(workbenchService, "WorkbenchService");
488
760
  const context = {
489
761
  eventBus: this.eventBus,
@@ -577,5 +849,7 @@ export {
577
849
  ExtensionRegistry,
578
850
  Pooder,
579
851
  ServiceRegistry,
852
+ ToolRegistryService,
853
+ ToolSessionService,
580
854
  WorkbenchService
581
855
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pooder/core",
3
- "version": "2.0.0",
3
+ "version": "2.1.0",
4
4
  "description": "Core logic for Pooder editor",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
package/src/command.ts CHANGED
@@ -1,10 +1,10 @@
1
- export interface CommandArgSchema {}
2
-
3
- export interface Command {
4
- id: string;
5
- handler: (...args: any[]) => any;
6
- title?: string;
7
- category?: string;
8
- description?: string;
9
- schema?: Record<string, CommandArgSchema>;
10
- }
1
+ export interface CommandArgSchema {}
2
+
3
+ export interface Command {
4
+ id: string;
5
+ handler: (...args: any[]) => any;
6
+ title?: string;
7
+ category?: string;
8
+ description?: string;
9
+ schema?: Record<string, CommandArgSchema>;
10
+ }
package/src/context.ts CHANGED
@@ -1,17 +1,17 @@
1
- import EventBus from "./event";
2
- import { Contribution } from "./contribution";
3
- import { Service } from "./service";
4
- import Disposable from "./disposable";
5
-
6
- interface ExtensionContext {
7
- readonly eventBus: EventBus;
8
- readonly services: {
9
- get<T extends Service>(serviceName: string): T | undefined;
10
- };
11
- readonly contributions: {
12
- get<T>(pointId: string): Contribution<T>[];
13
- register<T>(pointId: string, contribution: Contribution<T>): Disposable;
14
- };
15
- }
16
-
17
- export { ExtensionContext };
1
+ import EventBus from "./event";
2
+ import { Contribution } from "./contribution";
3
+ import { Service } from "./service";
4
+ import Disposable from "./disposable";
5
+
6
+ interface ExtensionContext {
7
+ readonly eventBus: EventBus;
8
+ readonly services: {
9
+ get<T extends Service>(serviceName: string): T | undefined;
10
+ };
11
+ readonly contributions: {
12
+ get<T>(pointId: string): Contribution<T>[];
13
+ register<T>(pointId: string, contribution: Contribution<T>): Disposable;
14
+ };
15
+ }
16
+
17
+ export { ExtensionContext };
@@ -1,12 +1,12 @@
1
- export * from "./points";
2
- export * from "./registry";
3
-
4
- export interface ContributionMetadata {
5
- name: string;
6
- extensionId: string;
7
- }
8
- export interface Contribution<T = any> {
9
- id: string;
10
- data: T;
11
- metadata?: Partial<ContributionMetadata>;
12
- }
1
+ export * from "./points";
2
+ export * from "./registry";
3
+
4
+ export interface ContributionMetadata {
5
+ name: string;
6
+ extensionId: string;
7
+ }
8
+ export interface Contribution<T = any> {
9
+ id: string;
10
+ data: T;
11
+ metadata?: Partial<ContributionMetadata>;
12
+ }
@@ -17,12 +17,36 @@ export interface CommandContribution {
17
17
  /**
18
18
  * Tool Contribution Data Definition
19
19
  */
20
+ export type ToolInteraction = "instant" | "session" | "hybrid";
21
+
22
+ export type ToolSessionLeavePolicy = "block" | "commit" | "rollback";
23
+
24
+ export interface ToolCommandBindings {
25
+ execute?: string;
26
+ begin?: string;
27
+ validate?: string;
28
+ commit?: string;
29
+ rollback?: string;
30
+ reset?: string;
31
+ }
32
+
20
33
  export interface ToolContribution {
21
34
  id: string;
22
35
  name: string;
23
- description: string;
24
- parameters?: Record<string, any>; // JSON Schema for parameters
25
- execute: (...args: any[]) => Promise<any>;
36
+ description?: string;
37
+ icon?: string;
38
+ interaction: ToolInteraction;
39
+ parameters?: Record<string, any>;
40
+ commands?: ToolCommandBindings;
41
+ view?: {
42
+ id?: string;
43
+ type?: "sidebar" | "panel" | "editor" | "dialog";
44
+ location?: string;
45
+ };
46
+ session?: {
47
+ autoBegin?: boolean;
48
+ leavePolicy?: ToolSessionLeavePolicy;
49
+ };
26
50
  }
27
51
 
28
52
  /**