@pooder/core 2.0.0 → 2.2.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.js CHANGED
@@ -20,6 +20,9 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
+ COMMAND_SERVICE: () => COMMAND_SERVICE,
24
+ CONFIGURATION_SERVICE: () => CONFIGURATION_SERVICE,
25
+ CORE_SERVICE_TOKENS: () => CORE_SERVICE_TOKENS,
23
26
  CommandService: () => CommandService,
24
27
  ConfigurationService: () => ConfigurationService,
25
28
  ContributionPointIds: () => ContributionPointIds,
@@ -29,27 +32,122 @@ __export(index_exports, {
29
32
  ExtensionRegistry: () => ExtensionRegistry,
30
33
  Pooder: () => Pooder,
31
34
  ServiceRegistry: () => ServiceRegistry,
32
- WorkbenchService: () => WorkbenchService
35
+ TOOL_REGISTRY_SERVICE: () => TOOL_REGISTRY_SERVICE,
36
+ TOOL_SESSION_SERVICE: () => TOOL_SESSION_SERVICE,
37
+ ToolRegistryService: () => ToolRegistryService,
38
+ ToolSessionService: () => ToolSessionService,
39
+ WORKBENCH_SERVICE: () => WORKBENCH_SERVICE,
40
+ WorkbenchService: () => WorkbenchService,
41
+ createServiceToken: () => createServiceToken,
42
+ isServiceToken: () => isServiceToken
33
43
  });
34
44
  module.exports = __toCommonJS(index_exports);
35
45
 
36
46
  // src/service.ts
47
+ function createServiceToken(name) {
48
+ if (!name) {
49
+ throw new Error("Service token name is required.");
50
+ }
51
+ return Object.freeze({
52
+ kind: "service-token",
53
+ key: Symbol(name),
54
+ name
55
+ });
56
+ }
57
+ function isServiceToken(identifier) {
58
+ return typeof identifier === "object" && identifier !== null && "kind" in identifier && identifier.kind === "service-token";
59
+ }
37
60
  var ServiceRegistry = class {
38
61
  constructor() {
39
- this.services = /* @__PURE__ */ new Map();
62
+ this.servicesByName = /* @__PURE__ */ new Map();
63
+ this.servicesByToken = /* @__PURE__ */ new Map();
64
+ this.registrationOrder = [];
40
65
  }
41
- register(name, service) {
42
- this.services.set(name, service);
66
+ register(identifier, service, options = {}) {
67
+ const normalized = this.normalizeIdentifier(identifier);
68
+ const existing = this.findEntry(normalized);
69
+ if (existing && !options.allowOverride) {
70
+ throw new Error(`Service "${normalized.name}" is already registered.`);
71
+ }
72
+ if (existing) {
73
+ this.removeEntry(existing);
74
+ }
75
+ const entry = {
76
+ token: normalized.token,
77
+ name: normalized.name,
78
+ service
79
+ };
80
+ this.servicesByName.set(entry.name, entry);
81
+ if (entry.token) {
82
+ this.servicesByToken.set(entry.token.key, entry);
83
+ }
84
+ this.registrationOrder.push(entry);
43
85
  return service;
44
86
  }
45
- get(serviceName) {
46
- return this.services.get(serviceName);
87
+ get(identifier) {
88
+ const normalized = this.normalizeIdentifier(identifier);
89
+ const entry = this.findEntry(normalized);
90
+ return entry == null ? void 0 : entry.service;
91
+ }
92
+ getOrThrow(identifier, errorMessage) {
93
+ const service = this.get(identifier);
94
+ if (service) {
95
+ return service;
96
+ }
97
+ const normalized = this.normalizeIdentifier(identifier);
98
+ throw new Error(errorMessage != null ? errorMessage : `Service "${normalized.name}" not found.`);
47
99
  }
48
- has(serviceName) {
49
- return this.services.has(serviceName);
100
+ has(identifier) {
101
+ const normalized = this.normalizeIdentifier(identifier);
102
+ return Boolean(this.findEntry(normalized));
50
103
  }
51
- delete(serviceName) {
52
- this.services.delete(serviceName);
104
+ delete(identifier) {
105
+ const normalized = this.normalizeIdentifier(identifier);
106
+ const entry = this.findEntry(normalized);
107
+ if (!entry) {
108
+ return false;
109
+ }
110
+ this.removeEntry(entry);
111
+ return true;
112
+ }
113
+ list() {
114
+ return this.registrationOrder.map((entry) => ({
115
+ id: entry.name,
116
+ token: entry.token,
117
+ service: entry.service
118
+ }));
119
+ }
120
+ clear() {
121
+ this.servicesByName.clear();
122
+ this.servicesByToken.clear();
123
+ this.registrationOrder.length = 0;
124
+ }
125
+ findEntry(identifier) {
126
+ var _a;
127
+ if (identifier.token) {
128
+ return (_a = this.servicesByToken.get(identifier.token.key)) != null ? _a : this.servicesByName.get(identifier.name);
129
+ }
130
+ return this.servicesByName.get(identifier.name);
131
+ }
132
+ normalizeIdentifier(identifier) {
133
+ if (isServiceToken(identifier)) {
134
+ return { token: identifier, name: identifier.name };
135
+ }
136
+ const name = identifier.trim();
137
+ if (!name) {
138
+ throw new Error("Service identifier must be a non-empty string.");
139
+ }
140
+ return { name };
141
+ }
142
+ removeEntry(entry) {
143
+ this.servicesByName.delete(entry.name);
144
+ if (entry.token) {
145
+ this.servicesByToken.delete(entry.token.key);
146
+ }
147
+ const index = this.registrationOrder.lastIndexOf(entry);
148
+ if (index >= 0) {
149
+ this.registrationOrder.splice(index, 1);
150
+ }
53
151
  }
54
152
  };
55
153
 
@@ -207,122 +305,6 @@ var ContributionRegistry = class {
207
305
  }
208
306
  };
209
307
 
210
- // src/extension.ts
211
- var ExtensionRegistry = class extends Map {
212
- };
213
- var ExtensionManager = class {
214
- constructor(context) {
215
- this.extensionRegistry = new ExtensionRegistry();
216
- this.extensionDisposables = /* @__PURE__ */ new Map();
217
- this.context = context;
218
- }
219
- register(extension) {
220
- if (this.extensionRegistry.has(extension.id)) {
221
- console.warn(
222
- `Plugin "${extension.id}" already registered. It will be overwritten.`
223
- );
224
- }
225
- this.extensionDisposables.set(extension.id, []);
226
- const disposables = this.extensionDisposables.get(extension.id);
227
- if (extension.contribute) {
228
- for (const [pointId, items] of Object.entries(extension.contribute())) {
229
- if (Array.isArray(items)) {
230
- items.forEach((item, index) => {
231
- const contributionId = item.id || (item.command ? item.command : `${extension.id}.${pointId}.${index}`);
232
- const contribution = {
233
- id: contributionId,
234
- metadata: {
235
- extensionId: extension.id,
236
- ...item == null ? void 0 : item.metadata
237
- },
238
- data: item
239
- };
240
- const disposable = this.context.contributions.register(
241
- pointId,
242
- contribution
243
- );
244
- disposables.push(disposable);
245
- const dispose = this.collectContribution(pointId, contribution);
246
- if (dispose) {
247
- disposables.push(dispose);
248
- }
249
- });
250
- }
251
- }
252
- }
253
- try {
254
- this.extensionRegistry.set(extension.id, extension);
255
- this.context.eventBus.emit("extension:register", extension);
256
- } catch (error) {
257
- console.error(
258
- `Error in onCreate hook for plugin "${extension.id}":`,
259
- error
260
- );
261
- }
262
- try {
263
- extension.activate(this.context);
264
- } catch (error) {
265
- console.error(
266
- `Error in onActivate hook for plugin "${extension.id}":`,
267
- error
268
- );
269
- }
270
- console.log(`Plugin "${extension.id}" registered successfully`);
271
- }
272
- collectContribution(pointId, item) {
273
- if (pointId === ContributionPointIds.CONFIGURATIONS) {
274
- const configService = this.context.services.get(
275
- "ConfigurationService"
276
- );
277
- configService == null ? void 0 : configService.initializeDefaults([item.data]);
278
- }
279
- if (pointId === ContributionPointIds.COMMANDS && item.data.handler) {
280
- const commandService = this.context.services.get("CommandService");
281
- return commandService.registerCommand(item.id, item.data.handler);
282
- }
283
- }
284
- unregister(name) {
285
- const extension = this.extensionRegistry.get(name);
286
- if (!extension) {
287
- console.warn(`Plugin "${name}" not found.`);
288
- return;
289
- }
290
- try {
291
- extension.deactivate(this.context);
292
- } catch (error) {
293
- console.error(`Error in deactivate for plugin "${name}":`, error);
294
- }
295
- const disposables = this.extensionDisposables.get(name);
296
- if (disposables) {
297
- disposables.forEach((d) => d.dispose());
298
- this.extensionDisposables.delete(name);
299
- }
300
- this.extensionRegistry.delete(name);
301
- console.log(`Plugin "${name}" unregistered`);
302
- return true;
303
- }
304
- enable(name) {
305
- const extension = this.extensionRegistry.get(name);
306
- if (!extension) {
307
- console.warn(`Plugin "${name}" not found.`);
308
- return;
309
- }
310
- }
311
- disable(name) {
312
- const extension = this.extensionRegistry.get(name);
313
- if (!extension) {
314
- console.warn(`Plugin "${name}" not found.`);
315
- return;
316
- }
317
- }
318
- update() {
319
- }
320
- destroy() {
321
- const extensionNames = Array.from(this.extensionRegistry.keys());
322
- extensionNames.forEach((name) => this.unregister(name));
323
- }
324
- };
325
-
326
308
  // src/services/CommandService.ts
327
309
  var CommandService = class {
328
310
  constructor() {
@@ -482,27 +464,486 @@ var ConfigurationService = class {
482
464
  }
483
465
  };
484
466
 
467
+ // src/services/ToolRegistryService.ts
468
+ var ToolRegistryService = class {
469
+ constructor() {
470
+ this.tools = /* @__PURE__ */ new Map();
471
+ }
472
+ registerTool(tool) {
473
+ if (!(tool == null ? void 0 : tool.id)) {
474
+ throw new Error("ToolContribution.id is required.");
475
+ }
476
+ this.tools.set(tool.id, tool);
477
+ return {
478
+ dispose: () => {
479
+ if (this.tools.get(tool.id) === tool) {
480
+ this.tools.delete(tool.id);
481
+ }
482
+ }
483
+ };
484
+ }
485
+ unregisterTool(toolId) {
486
+ this.tools.delete(toolId);
487
+ }
488
+ getTool(toolId) {
489
+ return this.tools.get(toolId);
490
+ }
491
+ listTools() {
492
+ return Array.from(this.tools.values());
493
+ }
494
+ hasTool(toolId) {
495
+ return this.tools.has(toolId);
496
+ }
497
+ dispose() {
498
+ this.tools.clear();
499
+ }
500
+ };
501
+
502
+ // src/services/tokens.ts
503
+ var COMMAND_SERVICE = createServiceToken(
504
+ "CommandService"
505
+ );
506
+ var CONFIGURATION_SERVICE = createServiceToken(
507
+ "ConfigurationService"
508
+ );
509
+ var TOOL_REGISTRY_SERVICE = createServiceToken("ToolRegistryService");
510
+ var TOOL_SESSION_SERVICE = createServiceToken("ToolSessionService");
511
+ var WORKBENCH_SERVICE = createServiceToken("WorkbenchService");
512
+ var CORE_SERVICE_TOKENS = {
513
+ COMMAND: COMMAND_SERVICE,
514
+ CONFIGURATION: CONFIGURATION_SERVICE,
515
+ TOOL_REGISTRY: TOOL_REGISTRY_SERVICE,
516
+ TOOL_SESSION: TOOL_SESSION_SERVICE,
517
+ WORKBENCH: WORKBENCH_SERVICE
518
+ };
519
+
520
+ // src/services/ToolSessionService.ts
521
+ var ToolSessionService = class {
522
+ constructor(dependencies = {}) {
523
+ this.sessions = /* @__PURE__ */ new Map();
524
+ this.dirtyTrackers = /* @__PURE__ */ new Map();
525
+ this.commandService = dependencies.commandService;
526
+ this.toolRegistry = dependencies.toolRegistry;
527
+ }
528
+ init(context) {
529
+ var _a, _b;
530
+ (_a = this.commandService) != null ? _a : this.commandService = context.get(COMMAND_SERVICE);
531
+ (_b = this.toolRegistry) != null ? _b : this.toolRegistry = context.get(TOOL_REGISTRY_SERVICE);
532
+ if (!this.commandService) {
533
+ throw new Error("ToolSessionService requires CommandService.");
534
+ }
535
+ if (!this.toolRegistry) {
536
+ throw new Error("ToolSessionService requires ToolRegistryService.");
537
+ }
538
+ }
539
+ setCommandService(commandService) {
540
+ this.commandService = commandService;
541
+ }
542
+ setToolRegistry(toolRegistry) {
543
+ this.toolRegistry = toolRegistry;
544
+ }
545
+ registerDirtyTracker(toolId, callback) {
546
+ const wrapped = () => {
547
+ try {
548
+ return callback();
549
+ } catch (e) {
550
+ return false;
551
+ }
552
+ };
553
+ this.dirtyTrackers.set(toolId, wrapped);
554
+ return {
555
+ dispose: () => {
556
+ if (this.dirtyTrackers.get(toolId) === wrapped) {
557
+ this.dirtyTrackers.delete(toolId);
558
+ }
559
+ }
560
+ };
561
+ }
562
+ ensureSession(toolId) {
563
+ const existing = this.sessions.get(toolId);
564
+ if (existing) return existing;
565
+ const created = {
566
+ toolId,
567
+ status: "idle",
568
+ dirty: false
569
+ };
570
+ this.sessions.set(toolId, created);
571
+ return created;
572
+ }
573
+ getState(toolId) {
574
+ return { ...this.ensureSession(toolId) };
575
+ }
576
+ isDirty(toolId) {
577
+ const tracker = this.dirtyTrackers.get(toolId);
578
+ if (tracker) return tracker();
579
+ return this.ensureSession(toolId).dirty;
580
+ }
581
+ markDirty(toolId, dirty = true) {
582
+ const session = this.ensureSession(toolId);
583
+ session.dirty = dirty;
584
+ session.lastUpdatedAt = Date.now();
585
+ }
586
+ resolveTool(toolId) {
587
+ return this.getToolRegistry().getTool(toolId);
588
+ }
589
+ async runCommand(commandId, ...args) {
590
+ if (!commandId) return void 0;
591
+ return await this.getCommandService().executeCommand(commandId, ...args);
592
+ }
593
+ getCommandService() {
594
+ if (!this.commandService) {
595
+ throw new Error("ToolSessionService is not initialized.");
596
+ }
597
+ return this.commandService;
598
+ }
599
+ getToolRegistry() {
600
+ if (!this.toolRegistry) {
601
+ throw new Error("ToolSessionService is not initialized.");
602
+ }
603
+ return this.toolRegistry;
604
+ }
605
+ async begin(toolId) {
606
+ var _a;
607
+ const tool = this.resolveTool(toolId);
608
+ const session = this.ensureSession(toolId);
609
+ if (session.status === "active") return;
610
+ await this.runCommand((_a = tool == null ? void 0 : tool.commands) == null ? void 0 : _a.begin);
611
+ session.status = "active";
612
+ session.startedAt = Date.now();
613
+ session.lastUpdatedAt = session.startedAt;
614
+ }
615
+ async validate(toolId) {
616
+ var _a;
617
+ const tool = this.resolveTool(toolId);
618
+ if (!((_a = tool == null ? void 0 : tool.commands) == null ? void 0 : _a.validate)) {
619
+ return { ok: true };
620
+ }
621
+ const result = await this.runCommand(tool.commands.validate);
622
+ if (result === false) return { ok: false, result };
623
+ if (result && typeof result === "object" && "ok" in result) {
624
+ return { ok: Boolean(result.ok), result };
625
+ }
626
+ return { ok: true, result };
627
+ }
628
+ async commit(toolId) {
629
+ var _a;
630
+ const tool = this.resolveTool(toolId);
631
+ const validateResult = await this.validate(toolId);
632
+ if (!validateResult.ok) return validateResult;
633
+ const result = await this.runCommand((_a = tool == null ? void 0 : tool.commands) == null ? void 0 : _a.commit);
634
+ const session = this.ensureSession(toolId);
635
+ session.dirty = false;
636
+ session.status = "idle";
637
+ session.lastUpdatedAt = Date.now();
638
+ return { ok: true, result };
639
+ }
640
+ async rollback(toolId) {
641
+ var _a, _b;
642
+ const tool = this.resolveTool(toolId);
643
+ 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));
644
+ const session = this.ensureSession(toolId);
645
+ session.dirty = false;
646
+ session.status = "idle";
647
+ session.lastUpdatedAt = Date.now();
648
+ }
649
+ deactivateSession(toolId) {
650
+ const session = this.ensureSession(toolId);
651
+ session.status = "idle";
652
+ session.lastUpdatedAt = Date.now();
653
+ }
654
+ async handleBeforeLeave(toolId) {
655
+ var _a, _b;
656
+ const tool = this.resolveTool(toolId);
657
+ if (!tool) return { decision: "allow" };
658
+ if (tool.interaction !== "session") return { decision: "allow" };
659
+ const dirty = this.isDirty(toolId);
660
+ if (!dirty) return { decision: "allow" };
661
+ const leavePolicy = (_b = (_a = tool.session) == null ? void 0 : _a.leavePolicy) != null ? _b : "block";
662
+ if (leavePolicy === "commit") {
663
+ const committed = await this.commit(toolId);
664
+ if (!committed.ok) {
665
+ return { decision: "blocked", reason: "session-validation-failed" };
666
+ }
667
+ return { decision: "allow" };
668
+ }
669
+ if (leavePolicy === "rollback") {
670
+ await this.rollback(toolId);
671
+ return { decision: "allow" };
672
+ }
673
+ return { decision: "blocked", reason: "session-dirty" };
674
+ }
675
+ dispose() {
676
+ this.sessions.clear();
677
+ this.dirtyTrackers.clear();
678
+ }
679
+ };
680
+
485
681
  // src/services/WorkbenchService.ts
486
682
  var WorkbenchService = class {
487
- constructor() {
683
+ constructor(dependencies = {}) {
488
684
  this._activeToolId = null;
685
+ this.guards = [];
686
+ this.eventBus = dependencies.eventBus;
687
+ this.toolRegistry = dependencies.toolRegistry;
688
+ this.sessionService = dependencies.sessionService;
489
689
  }
490
- init() {
690
+ init(context) {
691
+ var _a, _b, _c;
692
+ (_a = this.eventBus) != null ? _a : this.eventBus = context.eventBus;
693
+ (_b = this.toolRegistry) != null ? _b : this.toolRegistry = context.get(TOOL_REGISTRY_SERVICE);
694
+ (_c = this.sessionService) != null ? _c : this.sessionService = context.get(TOOL_SESSION_SERVICE);
695
+ if (!this.eventBus) {
696
+ throw new Error("WorkbenchService requires EventBus.");
697
+ }
698
+ if (!this.toolRegistry) {
699
+ throw new Error("WorkbenchService requires ToolRegistryService.");
700
+ }
701
+ if (!this.sessionService) {
702
+ throw new Error("WorkbenchService requires ToolSessionService.");
703
+ }
491
704
  }
492
705
  dispose() {
706
+ this.guards = [];
493
707
  }
494
708
  setEventBus(bus) {
495
709
  this.eventBus = bus;
496
710
  }
711
+ setToolRegistry(toolRegistry) {
712
+ this.toolRegistry = toolRegistry;
713
+ }
714
+ setToolSessionService(sessionService) {
715
+ this.sessionService = sessionService;
716
+ }
497
717
  get activeToolId() {
498
718
  return this._activeToolId;
499
719
  }
500
- activate(id) {
720
+ registerSwitchGuard(guard, priority = 0) {
721
+ const item = { guard, priority };
722
+ this.guards.push(item);
723
+ this.guards.sort((a, b) => b.priority - a.priority);
724
+ return {
725
+ dispose: () => {
726
+ const index = this.guards.indexOf(item);
727
+ if (index >= 0) this.guards.splice(index, 1);
728
+ }
729
+ };
730
+ }
731
+ async runGuards(context) {
732
+ for (const { guard } of this.guards) {
733
+ const allowed = await Promise.resolve(guard(context));
734
+ if (!allowed) return false;
735
+ }
736
+ return true;
737
+ }
738
+ async switchTool(id, options) {
501
739
  var _a;
502
- if (this._activeToolId === id) return;
740
+ const eventBus = this.getEventBus();
741
+ const toolRegistry = this.getToolRegistry();
742
+ const sessionService = this.getSessionService();
743
+ if (this._activeToolId === id) {
744
+ return { ok: true, from: this._activeToolId, to: id };
745
+ }
746
+ if (id && !toolRegistry.hasTool(id)) {
747
+ return {
748
+ ok: false,
749
+ from: this._activeToolId,
750
+ to: id,
751
+ reason: `tool-not-registered:${id}`
752
+ };
753
+ }
754
+ const context = {
755
+ from: this._activeToolId,
756
+ to: id,
757
+ reason: options == null ? void 0 : options.reason
758
+ };
759
+ const guardAllowed = await this.runGuards(context);
760
+ if (!guardAllowed) {
761
+ eventBus.emit("tool:switch:blocked", {
762
+ ...context,
763
+ reason: "blocked-by-guard"
764
+ });
765
+ return {
766
+ ok: false,
767
+ from: this._activeToolId,
768
+ to: id,
769
+ reason: "blocked-by-guard"
770
+ };
771
+ }
772
+ if (context.from) {
773
+ const leaveResult = await sessionService.handleBeforeLeave(context.from);
774
+ if (leaveResult.decision === "blocked") {
775
+ eventBus.emit("tool:switch:blocked", {
776
+ ...context,
777
+ reason: leaveResult.reason || "session-blocked"
778
+ });
779
+ return {
780
+ ok: false,
781
+ from: this._activeToolId,
782
+ to: id,
783
+ reason: leaveResult.reason || "session-blocked"
784
+ };
785
+ }
786
+ sessionService.deactivateSession(context.from);
787
+ }
788
+ if (id) {
789
+ const tool = toolRegistry.getTool(id);
790
+ if ((tool == null ? void 0 : tool.interaction) === "session" && ((_a = tool.session) == null ? void 0 : _a.autoBegin) !== false) {
791
+ await sessionService.begin(id);
792
+ }
793
+ }
503
794
  const previous = this._activeToolId;
504
795
  this._activeToolId = id;
505
- (_a = this.eventBus) == null ? void 0 : _a.emit("tool:activated", { id, previous });
796
+ const reason = options == null ? void 0 : options.reason;
797
+ eventBus.emit("tool:activated", { id, previous, reason });
798
+ eventBus.emit("tool:switch", { from: previous, to: id, reason });
799
+ return { ok: true, from: previous, to: id };
800
+ }
801
+ async activate(id) {
802
+ return await this.switchTool(id, { reason: "activate" });
803
+ }
804
+ async deactivate() {
805
+ return await this.switchTool(null, { reason: "deactivate" });
806
+ }
807
+ getEventBus() {
808
+ if (!this.eventBus) {
809
+ throw new Error("WorkbenchService is not initialized.");
810
+ }
811
+ return this.eventBus;
812
+ }
813
+ getToolRegistry() {
814
+ if (!this.toolRegistry) {
815
+ throw new Error("WorkbenchService is not initialized.");
816
+ }
817
+ return this.toolRegistry;
818
+ }
819
+ getSessionService() {
820
+ if (!this.sessionService) {
821
+ throw new Error("WorkbenchService is not initialized.");
822
+ }
823
+ return this.sessionService;
824
+ }
825
+ };
826
+
827
+ // src/extension.ts
828
+ var ExtensionRegistry = class extends Map {
829
+ };
830
+ var ExtensionManager = class {
831
+ constructor(context) {
832
+ this.extensionRegistry = new ExtensionRegistry();
833
+ this.extensionDisposables = /* @__PURE__ */ new Map();
834
+ this.context = context;
835
+ }
836
+ register(extension) {
837
+ if (this.extensionRegistry.has(extension.id)) {
838
+ console.warn(
839
+ `Plugin "${extension.id}" already registered. It will be overwritten.`
840
+ );
841
+ }
842
+ this.extensionDisposables.set(extension.id, []);
843
+ const disposables = this.extensionDisposables.get(extension.id);
844
+ if (extension.contribute) {
845
+ for (const [pointId, items] of Object.entries(extension.contribute())) {
846
+ if (Array.isArray(items)) {
847
+ items.forEach((item, index) => {
848
+ const contributionId = item.id || (item.command ? item.command : `${extension.id}.${pointId}.${index}`);
849
+ const contribution = {
850
+ id: contributionId,
851
+ metadata: {
852
+ extensionId: extension.id,
853
+ ...item == null ? void 0 : item.metadata
854
+ },
855
+ data: item
856
+ };
857
+ const disposable = this.context.contributions.register(
858
+ pointId,
859
+ contribution
860
+ );
861
+ disposables.push(disposable);
862
+ const dispose = this.collectContribution(pointId, contribution);
863
+ if (dispose) {
864
+ disposables.push(dispose);
865
+ }
866
+ });
867
+ }
868
+ }
869
+ }
870
+ try {
871
+ this.extensionRegistry.set(extension.id, extension);
872
+ this.context.eventBus.emit("extension:register", extension);
873
+ } catch (error) {
874
+ console.error(
875
+ `Error in onCreate hook for plugin "${extension.id}":`,
876
+ error
877
+ );
878
+ }
879
+ try {
880
+ extension.activate(this.context);
881
+ } catch (error) {
882
+ console.error(
883
+ `Error in onActivate hook for plugin "${extension.id}":`,
884
+ error
885
+ );
886
+ }
887
+ console.log(`Plugin "${extension.id}" registered successfully`);
888
+ }
889
+ collectContribution(pointId, item) {
890
+ if (pointId === ContributionPointIds.CONFIGURATIONS) {
891
+ const configService = this.context.services.get(
892
+ CONFIGURATION_SERVICE
893
+ );
894
+ configService == null ? void 0 : configService.initializeDefaults([item.data]);
895
+ }
896
+ if (pointId === ContributionPointIds.COMMANDS && item.data.handler) {
897
+ const commandService = this.context.services.get(COMMAND_SERVICE);
898
+ return commandService.registerCommand(item.id, item.data.handler);
899
+ }
900
+ if (pointId === ContributionPointIds.TOOLS) {
901
+ const toolRegistry = this.context.services.get(
902
+ TOOL_REGISTRY_SERVICE
903
+ );
904
+ if (!toolRegistry) return;
905
+ return toolRegistry.registerTool(item.data);
906
+ }
907
+ }
908
+ unregister(name) {
909
+ const extension = this.extensionRegistry.get(name);
910
+ if (!extension) {
911
+ console.warn(`Plugin "${name}" not found.`);
912
+ return;
913
+ }
914
+ try {
915
+ extension.deactivate(this.context);
916
+ } catch (error) {
917
+ console.error(`Error in deactivate for plugin "${name}":`, error);
918
+ }
919
+ const disposables = this.extensionDisposables.get(name);
920
+ if (disposables) {
921
+ disposables.forEach((d) => d.dispose());
922
+ this.extensionDisposables.delete(name);
923
+ }
924
+ this.extensionRegistry.delete(name);
925
+ console.log(`Plugin "${name}" unregistered`);
926
+ return true;
927
+ }
928
+ enable(name) {
929
+ const extension = this.extensionRegistry.get(name);
930
+ if (!extension) {
931
+ console.warn(`Plugin "${name}" not found.`);
932
+ return;
933
+ }
934
+ }
935
+ disable(name) {
936
+ const extension = this.extensionRegistry.get(name);
937
+ if (!extension) {
938
+ console.warn(`Plugin "${name}" not found.`);
939
+ return;
940
+ }
941
+ }
942
+ update() {
943
+ }
944
+ destroy() {
945
+ const extensionNames = Array.from(this.extensionRegistry.keys());
946
+ extensionNames.forEach((name) => this.unregister(name));
506
947
  }
507
948
  };
508
949
 
@@ -511,19 +952,37 @@ var Pooder = class {
511
952
  constructor() {
512
953
  this.eventBus = new event_default();
513
954
  this.services = new ServiceRegistry();
955
+ this.serviceContext = {
956
+ eventBus: this.eventBus,
957
+ get: (identifier) => this.services.get(identifier),
958
+ getOrThrow: (identifier, errorMessage) => this.services.getOrThrow(identifier, errorMessage),
959
+ has: (identifier) => this.services.has(identifier)
960
+ };
514
961
  this.contributions = new ContributionRegistry();
515
962
  this.initDefaultContributionPoints();
516
963
  const commandService = new CommandService();
517
- this.registerService(commandService, "CommandService");
964
+ this.registerService(commandService, CORE_SERVICE_TOKENS.COMMAND);
518
965
  const configurationService = new ConfigurationService();
519
- this.registerService(configurationService, "ConfigurationService");
520
- const workbenchService = new WorkbenchService();
521
- workbenchService.setEventBus(this.eventBus);
522
- this.registerService(workbenchService, "WorkbenchService");
966
+ this.registerService(configurationService, CORE_SERVICE_TOKENS.CONFIGURATION);
967
+ const toolRegistryService = new ToolRegistryService();
968
+ this.registerService(toolRegistryService, CORE_SERVICE_TOKENS.TOOL_REGISTRY);
969
+ const toolSessionService = new ToolSessionService({
970
+ commandService,
971
+ toolRegistry: toolRegistryService
972
+ });
973
+ this.registerService(toolSessionService, CORE_SERVICE_TOKENS.TOOL_SESSION);
974
+ const workbenchService = new WorkbenchService({
975
+ eventBus: this.eventBus,
976
+ toolRegistry: toolRegistryService,
977
+ sessionService: toolSessionService
978
+ });
979
+ this.registerService(workbenchService, CORE_SERVICE_TOKENS.WORKBENCH);
523
980
  const context = {
524
981
  eventBus: this.eventBus,
525
982
  services: {
526
- get: (serviceName) => this.services.get(serviceName)
983
+ get: (identifier) => this.services.get(identifier),
984
+ getOrThrow: (identifier, errorMessage) => this.services.getOrThrow(identifier, errorMessage),
985
+ has: (identifier) => this.services.has(identifier)
527
986
  },
528
987
  contributions: {
529
988
  get: (pointId) => this.getContributions(pointId),
@@ -555,38 +1014,102 @@ var Pooder = class {
555
1014
  });
556
1015
  }
557
1016
  // --- Service Management ---
558
- registerService(service, id) {
559
- var _a;
560
- const serviceId = id || service.constructor.name;
1017
+ registerService(service, identifier, options = {}) {
1018
+ const serviceIdentifier = this.resolveServiceIdentifier(service, identifier);
1019
+ const serviceId = this.getServiceLabel(serviceIdentifier);
561
1020
  try {
562
- (_a = service == null ? void 0 : service.init) == null ? void 0 : _a.call(service);
563
- } catch (e) {
564
- console.error(`Error initializing service ${serviceId}:`, e);
1021
+ const initResult = this.invokeServiceHook(service, "init");
1022
+ if (this.isPromiseLike(initResult)) {
1023
+ throw new Error(
1024
+ `Service "${serviceId}" init() is async. Use registerServiceAsync() instead.`
1025
+ );
1026
+ }
1027
+ this.services.register(serviceIdentifier, service, options);
1028
+ this.eventBus.emit("service:register", service, { id: serviceId });
1029
+ return true;
1030
+ } catch (error) {
1031
+ console.error(`Error initializing service ${serviceId}:`, error);
1032
+ return false;
1033
+ }
1034
+ }
1035
+ async registerServiceAsync(service, identifier, options = {}) {
1036
+ const serviceIdentifier = this.resolveServiceIdentifier(service, identifier);
1037
+ const serviceId = this.getServiceLabel(serviceIdentifier);
1038
+ try {
1039
+ await this.invokeServiceHookAsync(service, "init");
1040
+ this.services.register(serviceIdentifier, service, options);
1041
+ this.eventBus.emit("service:register", service, { id: serviceId });
1042
+ return true;
1043
+ } catch (error) {
1044
+ console.error(`Error initializing service ${serviceId}:`, error);
565
1045
  return false;
566
1046
  }
567
- this.services.register(serviceId, service);
568
- this.eventBus.emit("service:register", service);
1047
+ }
1048
+ unregisterService(serviceOrIdentifier, id) {
1049
+ const resolvedIdentifier = this.resolveUnregisterIdentifier(
1050
+ serviceOrIdentifier,
1051
+ id
1052
+ );
1053
+ const serviceId = this.getServiceLabel(resolvedIdentifier);
1054
+ const registeredService = this.services.get(resolvedIdentifier);
1055
+ if (!registeredService) {
1056
+ console.warn(`Service ${serviceId} is not registered.`);
1057
+ return true;
1058
+ }
1059
+ try {
1060
+ const disposeResult = this.invokeServiceHook(registeredService, "dispose");
1061
+ if (this.isPromiseLike(disposeResult)) {
1062
+ throw new Error(
1063
+ `Service "${serviceId}" dispose() is async. Use unregisterServiceAsync() instead.`
1064
+ );
1065
+ }
1066
+ } catch (error) {
1067
+ console.error(`Error disposing service ${serviceId}:`, error);
1068
+ return false;
1069
+ }
1070
+ this.services.delete(resolvedIdentifier);
1071
+ this.eventBus.emit("service:unregister", registeredService, { id: serviceId });
569
1072
  return true;
570
1073
  }
571
- unregisterService(service, id) {
572
- var _a;
573
- const serviceId = id || service.constructor.name;
574
- if (!this.services.has(serviceId)) {
1074
+ async unregisterServiceAsync(serviceOrIdentifier, id) {
1075
+ const resolvedIdentifier = this.resolveUnregisterIdentifier(
1076
+ serviceOrIdentifier,
1077
+ id
1078
+ );
1079
+ const serviceId = this.getServiceLabel(resolvedIdentifier);
1080
+ const registeredService = this.services.get(resolvedIdentifier);
1081
+ if (!registeredService) {
575
1082
  console.warn(`Service ${serviceId} is not registered.`);
576
1083
  return true;
577
1084
  }
578
1085
  try {
579
- (_a = service == null ? void 0 : service.dispose) == null ? void 0 : _a.call(service);
580
- } catch (e) {
581
- console.error(`Error disposing service ${serviceId}:`, e);
1086
+ await this.invokeServiceHookAsync(registeredService, "dispose");
1087
+ } catch (error) {
1088
+ console.error(`Error disposing service ${serviceId}:`, error);
582
1089
  return false;
583
1090
  }
584
- this.services.delete(serviceId);
585
- this.eventBus.emit("service:unregister", service);
1091
+ this.services.delete(resolvedIdentifier);
1092
+ this.eventBus.emit("service:unregister", registeredService, { id: serviceId });
586
1093
  return true;
587
1094
  }
588
- getService(id) {
589
- return this.services.get(id);
1095
+ getService(identifier) {
1096
+ return this.services.get(identifier);
1097
+ }
1098
+ getServiceOrThrow(identifier, errorMessage) {
1099
+ return this.services.getOrThrow(identifier, errorMessage);
1100
+ }
1101
+ hasService(identifier) {
1102
+ return this.services.has(identifier);
1103
+ }
1104
+ async dispose() {
1105
+ var _a;
1106
+ this.extensionManager.destroy();
1107
+ const registrations = this.services.list().slice().reverse();
1108
+ for (const item of registrations) {
1109
+ const identifier = (_a = item.token) != null ? _a : item.id;
1110
+ await this.unregisterServiceAsync(identifier);
1111
+ }
1112
+ this.services.clear();
590
1113
  }
591
1114
  // --- Contribution Management ---
592
1115
  registerContributionPoint(point) {
@@ -601,9 +1124,40 @@ var Pooder = class {
601
1124
  getContributions(pointId) {
602
1125
  return this.contributions.get(pointId);
603
1126
  }
1127
+ resolveServiceIdentifier(service, identifier) {
1128
+ return identifier != null ? identifier : service.constructor.name;
1129
+ }
1130
+ resolveUnregisterIdentifier(serviceOrIdentifier, id) {
1131
+ if (typeof serviceOrIdentifier === "string" || isServiceToken(serviceOrIdentifier)) {
1132
+ return serviceOrIdentifier;
1133
+ }
1134
+ return id != null ? id : serviceOrIdentifier.constructor.name;
1135
+ }
1136
+ getServiceLabel(identifier) {
1137
+ if (typeof identifier === "string") {
1138
+ return identifier;
1139
+ }
1140
+ return identifier.name;
1141
+ }
1142
+ invokeServiceHook(service, hook) {
1143
+ const handler = service[hook];
1144
+ if (!handler) {
1145
+ return;
1146
+ }
1147
+ return handler.call(service, this.serviceContext);
1148
+ }
1149
+ async invokeServiceHookAsync(service, hook) {
1150
+ await this.invokeServiceHook(service, hook);
1151
+ }
1152
+ isPromiseLike(value) {
1153
+ return typeof value === "object" && value !== null && "then" in value && typeof value.then === "function";
1154
+ }
604
1155
  };
605
1156
  // Annotate the CommonJS export names for ESM import in node:
606
1157
  0 && (module.exports = {
1158
+ COMMAND_SERVICE,
1159
+ CONFIGURATION_SERVICE,
1160
+ CORE_SERVICE_TOKENS,
607
1161
  CommandService,
608
1162
  ConfigurationService,
609
1163
  ContributionPointIds,
@@ -613,5 +1167,12 @@ var Pooder = class {
613
1167
  ExtensionRegistry,
614
1168
  Pooder,
615
1169
  ServiceRegistry,
616
- WorkbenchService
1170
+ TOOL_REGISTRY_SERVICE,
1171
+ TOOL_SESSION_SERVICE,
1172
+ ToolRegistryService,
1173
+ ToolSessionService,
1174
+ WORKBENCH_SERVICE,
1175
+ WorkbenchService,
1176
+ createServiceToken,
1177
+ isServiceToken
617
1178
  });