@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/CHANGELOG.md +6 -0
- package/dist/index.d.mts +100 -4
- package/dist/index.d.ts +100 -4
- package/dist/index.js +280 -4
- package/dist/index.mjs +278 -4
- package/package.json +1 -1
- package/src/command.ts +10 -10
- package/src/context.ts +17 -17
- package/src/contribution/index.ts +12 -12
- package/src/contribution/points.ts +27 -3
- package/src/contribution/registry.ts +118 -118
- package/src/disposable.ts +3 -3
- package/src/extension.ts +171 -164
- package/src/index.ts +161 -145
- package/src/run-test-full.ts +98 -98
- package/src/services/CommandService.ts +79 -79
- package/src/services/ConfigurationService.ts +107 -107
- package/src/services/ToolRegistryService.ts +41 -0
- package/src/services/ToolSessionService.ts +176 -0
- package/src/services/WorkbenchService.ts +138 -9
- package/src/services/index.ts +9 -1
- package/src/test-extension-full.ts +79 -79
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
|
-
|
|
466
|
-
|
|
467
|
-
|
|
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
|
-
|
|
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
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
|
|
24
|
-
|
|
25
|
-
|
|
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
|
/**
|