maskweaver 0.8.5 → 0.8.7
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/README.ko.md +214 -186
- package/README.md +232 -204
- package/assets/agents/squad-operator.md +56 -56
- package/assets/commands/weave-approve-plan.md +57 -57
- package/assets/commands/weave-craft.md +43 -43
- package/assets/commands/weave-design.md +64 -64
- package/assets/commands/weave-flow.md +48 -48
- package/assets/commands/weave-help.md +101 -101
- package/assets/commands/weave-init.md +23 -23
- package/assets/commands/weave-plan.md +15 -15
- package/assets/commands/weave-prepare.md +69 -69
- package/assets/commands/weave-refine-plan.md +59 -59
- package/assets/commands/weave-repair.md +70 -70
- package/assets/commands/weave-research.md +51 -51
- package/assets/commands/weave-spec.md +227 -227
- package/assets/commands/weave-status.md +2 -2
- package/assets/commands/weave-verify.md +44 -44
- package/assets/commands/weave-worktree.md +69 -69
- package/dist/cli/doctor.d.ts +16 -0
- package/dist/cli/doctor.d.ts.map +1 -0
- package/dist/cli/doctor.js +355 -0
- package/dist/cli/doctor.js.map +1 -0
- package/dist/cli/install.js +9 -0
- package/dist/cli/install.js.map +1 -1
- package/dist/plugin/config/index.d.ts +26 -10
- package/dist/plugin/config/index.d.ts.map +1 -1
- package/dist/plugin/config/index.js +36 -15
- package/dist/plugin/config/index.js.map +1 -1
- package/dist/plugin/index.d.ts +1 -27
- package/dist/plugin/index.d.ts.map +1 -1
- package/dist/plugin/index.js +118 -254
- package/dist/plugin/index.js.map +1 -1
- package/dist/plugin/tools/slashcommand.js +59 -59
- package/dist/plugin/tools/squad.js +3 -3
- package/dist/plugin/tools/weave.d.ts +22 -1
- package/dist/plugin/tools/weave.d.ts.map +1 -1
- package/dist/plugin/tools/weave.js +1223 -126
- package/dist/plugin/tools/weave.js.map +1 -1
- package/dist/plugin/types.d.ts +10 -8
- package/dist/plugin/types.d.ts.map +1 -1
- package/dist/plugin/types.js +2 -0
- package/dist/plugin/types.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/dist/weave/change-artifacts.d.ts +25 -0
- package/dist/weave/change-artifacts.d.ts.map +1 -0
- package/dist/weave/change-artifacts.js +184 -0
- package/dist/weave/change-artifacts.js.map +1 -0
- package/dist/weave/loop.d.ts +99 -0
- package/dist/weave/loop.d.ts.map +1 -0
- package/dist/weave/loop.js +426 -0
- package/dist/weave/loop.js.map +1 -0
- package/dist/weave/phase-manager.d.ts.map +1 -1
- package/dist/weave/phase-manager.js +12 -0
- package/dist/weave/phase-manager.js.map +1 -1
- package/dist/weave/stages/archive.d.ts +14 -0
- package/dist/weave/stages/archive.d.ts.map +1 -0
- package/dist/weave/stages/archive.js +38 -0
- package/dist/weave/stages/archive.js.map +1 -0
- package/dist/weave/stages/execute.d.ts.map +1 -1
- package/dist/weave/stages/execute.js +19 -4
- package/dist/weave/stages/execute.js.map +1 -1
- package/dist/weave/stages/plan.d.ts.map +1 -1
- package/dist/weave/stages/plan.js +6 -0
- package/dist/weave/stages/plan.js.map +1 -1
- package/dist/weave/types.d.ts +60 -0
- package/dist/weave/types.d.ts.map +1 -1
- package/dist/weave/worktree.d.ts.map +1 -1
- package/dist/weave/worktree.js +19 -0
- package/dist/weave/worktree.js.map +1 -1
- package/package.json +60 -58
- package/postinstall.mjs +97 -0
package/dist/plugin/index.js
CHANGED
|
@@ -12,16 +12,13 @@
|
|
|
12
12
|
* Based on oh-my-opencode plugin development patterns.
|
|
13
13
|
*/
|
|
14
14
|
import { z } from 'zod';
|
|
15
|
-
// Inline shim: tool() is just an identity function in @opencode-ai/plugin
|
|
16
|
-
// We inline it to avoid bundler resolution bugs with the upstream package
|
|
17
|
-
const tool = (input) => input;
|
|
18
15
|
import * as fs from 'node:fs';
|
|
19
16
|
import * as path from 'node:path';
|
|
20
17
|
import * as os from 'node:os';
|
|
18
|
+
import { spawnSync } from 'node:child_process';
|
|
21
19
|
import { fileURLToPath } from 'node:url';
|
|
22
|
-
import { parse as parseYaml } from 'yaml';
|
|
23
20
|
import { VERSION } from '../version.js';
|
|
24
|
-
import { loadPluginConfig, isMaskEnabled, isToolEnabled, getDefaultMask, isAutoActivateEnabled,
|
|
21
|
+
import { loadPluginConfig, isMaskEnabled, isToolEnabled, getDefaultMask, isAutoActivateEnabled, isVerboseLoggingEnabled, isCompletionSoundEnabled, validateConfig, } from './config/index.js';
|
|
25
22
|
// New tool imports
|
|
26
23
|
import { createMemorySearchTool } from './tools/memorySearch.js';
|
|
27
24
|
import { createMemoryWriteTool } from './tools/memoryWrite.js';
|
|
@@ -424,8 +421,17 @@ function buildRichPrompt(mask) {
|
|
|
424
421
|
}
|
|
425
422
|
return parts.join('\n');
|
|
426
423
|
}
|
|
424
|
+
function pluginLog(client, level, message) {
|
|
425
|
+
client.app.log({
|
|
426
|
+
body: {
|
|
427
|
+
service: 'maskweaver',
|
|
428
|
+
level,
|
|
429
|
+
message,
|
|
430
|
+
},
|
|
431
|
+
});
|
|
432
|
+
}
|
|
427
433
|
// ============================================================================
|
|
428
|
-
//
|
|
434
|
+
// Helper functions for tool factories
|
|
429
435
|
// ============================================================================
|
|
430
436
|
function createListMasksTool(maskLoader, activeMask) {
|
|
431
437
|
return {
|
|
@@ -553,82 +559,56 @@ Active mask: ${active ? `${active.profile.name} (${active.metadata.id})` : 'none
|
|
|
553
559
|
},
|
|
554
560
|
};
|
|
555
561
|
}
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
if (
|
|
560
|
-
return
|
|
561
|
-
|
|
562
|
+
function getSessionId(event) {
|
|
563
|
+
if (typeof event.sessionID === 'string')
|
|
564
|
+
return event.sessionID;
|
|
565
|
+
if (typeof event.sessionId === 'string')
|
|
566
|
+
return event.sessionId;
|
|
567
|
+
return null;
|
|
568
|
+
}
|
|
569
|
+
function runSoundCommand(command, args) {
|
|
562
570
|
try {
|
|
563
|
-
const
|
|
564
|
-
|
|
565
|
-
|
|
571
|
+
const result = spawnSync(command, args, {
|
|
572
|
+
stdio: 'ignore',
|
|
573
|
+
windowsHide: true,
|
|
574
|
+
timeout: 2500,
|
|
575
|
+
});
|
|
576
|
+
return !result.error && result.status === 0;
|
|
566
577
|
}
|
|
567
|
-
catch
|
|
568
|
-
return
|
|
578
|
+
catch {
|
|
579
|
+
return false;
|
|
569
580
|
}
|
|
570
581
|
}
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
'
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
You are a pure execution agent. You accurately perform work instructions received from the Mask Weaver.
|
|
591
|
-
|
|
592
|
-
## Behavior Principles
|
|
593
|
-
|
|
594
|
-
1. If the Mask Weaver provides a **mask (persona)**, become that expert and work accordingly
|
|
595
|
-
2. If no mask is provided, work as a competent software engineer
|
|
596
|
-
3. Complete assigned tasks accurately
|
|
597
|
-
4. Report results clearly
|
|
598
|
-
|
|
599
|
-
## Result Reporting
|
|
600
|
-
|
|
601
|
-
When work is complete:
|
|
602
|
-
- Summary of work performed
|
|
603
|
-
- Generated outputs
|
|
604
|
-
- Additional considerations (if any)`,
|
|
605
|
-
},
|
|
606
|
-
};
|
|
607
|
-
function loadAgentAssets(...assetsDirs) {
|
|
608
|
-
// Start with default embedded agents (always available)
|
|
609
|
-
const agents = { ...DEFAULT_AGENTS };
|
|
610
|
-
// Load from each directory in order (later directories override earlier ones)
|
|
611
|
-
for (const assetsDir of assetsDirs) {
|
|
612
|
-
const agentsDir = path.join(assetsDir, 'agents');
|
|
613
|
-
if (!fs.existsSync(agentsDir))
|
|
614
|
-
continue;
|
|
582
|
+
function playCompletionSound(config) {
|
|
583
|
+
if (!isCompletionSoundEnabled(config))
|
|
584
|
+
return;
|
|
585
|
+
let played = false;
|
|
586
|
+
if (process.platform === 'win32') {
|
|
587
|
+
played = runSoundCommand('powershell', [
|
|
588
|
+
'-NoProfile',
|
|
589
|
+
'-NonInteractive',
|
|
590
|
+
'-Command',
|
|
591
|
+
'[console]::beep(880,220)',
|
|
592
|
+
]);
|
|
593
|
+
}
|
|
594
|
+
else if (process.platform === 'darwin') {
|
|
595
|
+
played = runSoundCommand('afplay', ['/System/Library/Sounds/Glass.aiff']);
|
|
596
|
+
}
|
|
597
|
+
else if (process.platform === 'linux') {
|
|
598
|
+
played = runSoundCommand('canberra-gtk-play', ['-i', 'complete', '-d', 'maskweaver']);
|
|
599
|
+
}
|
|
600
|
+
if (!played) {
|
|
615
601
|
try {
|
|
616
|
-
|
|
617
|
-
for (const file of files) {
|
|
618
|
-
if (file.endsWith('.md') && file !== 'dummy-template.md') {
|
|
619
|
-
const agentId = path.basename(file, '.md');
|
|
620
|
-
const content = fs.readFileSync(path.join(agentsDir, file), 'utf-8');
|
|
621
|
-
agents[agentId] = parseAgentMarkdown(content);
|
|
622
|
-
}
|
|
623
|
-
}
|
|
602
|
+
process.stdout.write('\u0007');
|
|
624
603
|
}
|
|
625
|
-
catch
|
|
626
|
-
// Ignore
|
|
604
|
+
catch {
|
|
605
|
+
// Ignore failures - notification sound is best-effort only.
|
|
627
606
|
}
|
|
628
607
|
}
|
|
629
|
-
return agents;
|
|
630
608
|
}
|
|
631
|
-
|
|
609
|
+
let state = null;
|
|
610
|
+
// ============================================================================
|
|
611
|
+
export const MaskweaverPlugin = async ({ client, directory, project, worktree, $, serverUrl }) => {
|
|
632
612
|
// ==========================================================================
|
|
633
613
|
// 1. Load Configuration (oh-my-opencode pattern)
|
|
634
614
|
// ==========================================================================
|
|
@@ -636,11 +616,7 @@ export const MaskweaverPlugin = async ({ client, directory }) => {
|
|
|
636
616
|
// Validate configuration
|
|
637
617
|
const configErrors = validateConfig(pluginConfig);
|
|
638
618
|
if (configErrors.length > 0) {
|
|
639
|
-
client.
|
|
640
|
-
service: 'maskweaver',
|
|
641
|
-
level: 'warn',
|
|
642
|
-
message: `Configuration validation errors: ${configErrors.join(', ')}`,
|
|
643
|
-
});
|
|
619
|
+
pluginLog(client, 'warn', `Configuration validation errors: ${configErrors.join(', ')}`);
|
|
644
620
|
}
|
|
645
621
|
const verbose = isVerboseLoggingEnabled(pluginConfig);
|
|
646
622
|
// ==========================================================================
|
|
@@ -650,24 +626,12 @@ export const MaskweaverPlugin = async ({ client, directory }) => {
|
|
|
650
626
|
// Track if this is a first-time installation
|
|
651
627
|
const isFirstInstall = installResult.installed.length > 0;
|
|
652
628
|
if (isFirstInstall) {
|
|
653
|
-
client.
|
|
654
|
-
service: 'maskweaver',
|
|
655
|
-
level: 'info',
|
|
656
|
-
message: `Installed ${installResult.installed.length} files to .opencode/ (agents, masks)`,
|
|
657
|
-
});
|
|
629
|
+
pluginLog(client, 'info', `Installed ${installResult.installed.length} files to .opencode/ (agents, masks)`);
|
|
658
630
|
// Show prominent restart message for first-time installation
|
|
659
|
-
client
|
|
660
|
-
service: 'maskweaver',
|
|
661
|
-
level: 'warn',
|
|
662
|
-
message: `⚠️ RESTART REQUIRED: Please restart OpenCode to activate all Maskweaver features (agents, masks, commands).`,
|
|
663
|
-
});
|
|
631
|
+
pluginLog(client, 'warn', `⚠️ RESTART REQUIRED: Please restart OpenCode to activate all Maskweaver features (agents, masks, commands).`);
|
|
664
632
|
}
|
|
665
633
|
if (installResult.errors.length > 0) {
|
|
666
|
-
client.
|
|
667
|
-
service: 'maskweaver',
|
|
668
|
-
level: 'warn',
|
|
669
|
-
message: `Asset errors: ${installResult.errors.join(', ')}`,
|
|
670
|
-
});
|
|
634
|
+
pluginLog(client, 'warn', `Asset errors: ${installResult.errors.join(', ')}`);
|
|
671
635
|
}
|
|
672
636
|
// ==========================================================================
|
|
673
637
|
// 3. Initialize masks
|
|
@@ -677,37 +641,27 @@ export const MaskweaverPlugin = async ({ client, directory }) => {
|
|
|
677
641
|
const projectMasksDir = path.join(directory, '.opencode', 'masks');
|
|
678
642
|
// Priority: project masks > global masks
|
|
679
643
|
const masksDir = fs.existsSync(projectMasksDir) ? projectMasksDir : globalMasksDir;
|
|
680
|
-
|
|
644
|
+
const pluginState = {
|
|
681
645
|
maskLoader: null,
|
|
682
646
|
activeMask: null,
|
|
683
647
|
masksDir,
|
|
684
648
|
config: pluginConfig,
|
|
649
|
+
currentSessionID: null,
|
|
685
650
|
};
|
|
651
|
+
state = pluginState;
|
|
686
652
|
// Log plugin loaded
|
|
687
|
-
client
|
|
688
|
-
service: 'maskweaver',
|
|
689
|
-
level: 'info',
|
|
690
|
-
message: `Maskweaver plugin loaded v${VERSION}`,
|
|
691
|
-
});
|
|
653
|
+
pluginLog(client, 'info', `Maskweaver plugin loaded v${VERSION}`);
|
|
692
654
|
if (fs.existsSync(masksDir)) {
|
|
693
|
-
|
|
655
|
+
pluginState.maskLoader = new MaskLoader(masksDir, pluginConfig);
|
|
694
656
|
try {
|
|
695
|
-
await
|
|
657
|
+
await pluginState.maskLoader.loadCatalog();
|
|
696
658
|
if (verbose) {
|
|
697
|
-
client
|
|
698
|
-
service: 'maskweaver',
|
|
699
|
-
level: 'info',
|
|
700
|
-
message: `Masks found at: ${masksDir}`,
|
|
701
|
-
});
|
|
659
|
+
pluginLog(client, 'info', `Masks found at: ${masksDir}`);
|
|
702
660
|
}
|
|
703
661
|
}
|
|
704
662
|
catch (e) {
|
|
705
|
-
client
|
|
706
|
-
|
|
707
|
-
level: 'warn',
|
|
708
|
-
message: `Failed to load masks: ${e}`,
|
|
709
|
-
});
|
|
710
|
-
state.maskLoader = null;
|
|
663
|
+
pluginLog(client, 'warn', `Failed to load masks: ${e}`);
|
|
664
|
+
pluginState.maskLoader = null;
|
|
711
665
|
}
|
|
712
666
|
}
|
|
713
667
|
// ==========================================================================
|
|
@@ -715,40 +669,27 @@ export const MaskweaverPlugin = async ({ client, directory }) => {
|
|
|
715
669
|
// ==========================================================================
|
|
716
670
|
const defaultMaskId = getDefaultMask(pluginConfig);
|
|
717
671
|
const autoActivate = isAutoActivateEnabled(pluginConfig);
|
|
718
|
-
if (defaultMaskId && autoActivate &&
|
|
672
|
+
if (defaultMaskId && autoActivate && pluginState.maskLoader) {
|
|
719
673
|
try {
|
|
720
|
-
const defaultMask = await
|
|
674
|
+
const defaultMask = await pluginState.maskLoader.load(defaultMaskId);
|
|
721
675
|
if (defaultMask) {
|
|
722
|
-
|
|
723
|
-
client.
|
|
724
|
-
service: 'maskweaver',
|
|
725
|
-
level: 'info',
|
|
726
|
-
message: `Auto-activated default mask: ${defaultMaskId} (${defaultMask.profile.name})`,
|
|
727
|
-
});
|
|
676
|
+
pluginState.activeMask = defaultMask;
|
|
677
|
+
pluginLog(client, 'info', `Auto-activated default mask: ${defaultMaskId} (${defaultMask.profile.name})`);
|
|
728
678
|
}
|
|
729
679
|
else {
|
|
730
|
-
client
|
|
731
|
-
service: 'maskweaver',
|
|
732
|
-
level: 'warn',
|
|
733
|
-
message: `Default mask "${defaultMaskId}" not found or disabled`,
|
|
734
|
-
});
|
|
680
|
+
pluginLog(client, 'warn', `Default mask "${defaultMaskId}" not found or disabled`);
|
|
735
681
|
}
|
|
736
682
|
}
|
|
737
683
|
catch (e) {
|
|
738
|
-
client
|
|
739
|
-
service: 'maskweaver',
|
|
740
|
-
level: 'warn',
|
|
741
|
-
message: `Failed to auto-activate default mask: ${e}`,
|
|
742
|
-
});
|
|
684
|
+
pluginLog(client, 'warn', `Failed to auto-activate default mask: ${e}`);
|
|
743
685
|
}
|
|
744
686
|
}
|
|
745
687
|
// ==========================================================================
|
|
746
688
|
// 5. Helper functions for tool factories
|
|
747
689
|
// ==========================================================================
|
|
748
|
-
const getActiveMask = () =>
|
|
690
|
+
const getActiveMask = () => pluginState.activeMask;
|
|
749
691
|
const setActiveMask = (mask) => {
|
|
750
|
-
|
|
751
|
-
state.activeMask = mask;
|
|
692
|
+
pluginState.activeMask = mask;
|
|
752
693
|
};
|
|
753
694
|
// ==========================================================================
|
|
754
695
|
// 6. Conditional tool registration (oh-my-opencode pattern)
|
|
@@ -756,28 +697,29 @@ export const MaskweaverPlugin = async ({ client, directory }) => {
|
|
|
756
697
|
const isToolActive = (toolName) => isToolEnabled(pluginConfig, toolName);
|
|
757
698
|
// Helper to ensure tool arguments are compatible with opencode's expected format.
|
|
758
699
|
// opencode expects a ZodRawShape (raw object), NOT a ZodObject instance.
|
|
700
|
+
// Zod 4: schema.def.shape, Zod 3: schema._def.shape()
|
|
759
701
|
const wrapSchema = (schema) => {
|
|
760
702
|
if (!schema || typeof schema !== 'object')
|
|
761
703
|
return schema;
|
|
762
|
-
//
|
|
763
|
-
if (schema.def && typeof schema.def === 'object' && schema.type === 'object') {
|
|
704
|
+
// Zod 4 — def.shape is a plain object
|
|
705
|
+
if (schema.def && typeof schema.def === 'object' && schema.type === 'object' && schema.def.shape && typeof schema.def.shape === 'object') {
|
|
764
706
|
return schema.def.shape;
|
|
765
707
|
}
|
|
766
|
-
//
|
|
708
|
+
// Zod 3 — _def.shape() returns a plain object
|
|
767
709
|
if (schema._def && typeof schema._def.shape === 'function') {
|
|
768
710
|
return schema._def.shape();
|
|
769
711
|
}
|
|
770
712
|
return schema;
|
|
771
713
|
};
|
|
772
714
|
const tools = {};
|
|
773
|
-
if (
|
|
715
|
+
if (pluginState.maskLoader) {
|
|
774
716
|
if (isToolActive('list_masks')) {
|
|
775
|
-
const tool = createListMasksTool(
|
|
717
|
+
const tool = createListMasksTool(pluginState.maskLoader, getActiveMask);
|
|
776
718
|
tool.args = wrapSchema(tool.args);
|
|
777
719
|
tools.list_masks = tool;
|
|
778
720
|
}
|
|
779
721
|
if (isToolActive('select_mask')) {
|
|
780
|
-
const tool = createSelectMaskTool(
|
|
722
|
+
const tool = createSelectMaskTool(pluginState.maskLoader, getActiveMask, setActiveMask);
|
|
781
723
|
tool.args = wrapSchema(tool.args);
|
|
782
724
|
tools.select_mask = tool;
|
|
783
725
|
}
|
|
@@ -787,13 +729,13 @@ export const MaskweaverPlugin = async ({ client, directory }) => {
|
|
|
787
729
|
tools.deselect_mask = tool;
|
|
788
730
|
}
|
|
789
731
|
if (isToolActive('get_mask_prompt')) {
|
|
790
|
-
const tool = createGetMaskPromptTool(
|
|
732
|
+
const tool = createGetMaskPromptTool(pluginState.maskLoader, getActiveMask);
|
|
791
733
|
tool.args = wrapSchema(tool.args);
|
|
792
734
|
tools.get_mask_prompt = tool;
|
|
793
735
|
}
|
|
794
736
|
}
|
|
795
737
|
if (isToolActive('maskweaver_status')) {
|
|
796
|
-
const tool = createMaskweaverStatusTool(
|
|
738
|
+
const tool = createMaskweaverStatusTool(pluginState.maskLoader, masksDir, getActiveMask);
|
|
797
739
|
tool.args = wrapSchema(tool.args);
|
|
798
740
|
tools.maskweaver_status = tool;
|
|
799
741
|
}
|
|
@@ -885,104 +827,20 @@ export const MaskweaverPlugin = async ({ client, directory }) => {
|
|
|
885
827
|
};
|
|
886
828
|
}
|
|
887
829
|
// ==========================================================================
|
|
888
|
-
// 8.
|
|
830
|
+
// 8. Agents are loaded from .opencode/agents/*.md files by OpenCode's
|
|
831
|
+
// filesystem-based agent loader (see config/agent.ts:110-140).
|
|
832
|
+
// installAssets() in step 2 copies agent .md files so they are picked up.
|
|
833
|
+
// The 'agent' property in Hooks is NOT consumed by OpenCode — confirmed
|
|
834
|
+
// by source analysis (packages/opencode/src/plugin/index.ts:92-103).
|
|
889
835
|
// ==========================================================================
|
|
890
|
-
const assetsDir = getAssetsDir();
|
|
891
|
-
const projectOpencodeDir = path.join(directory, '.opencode');
|
|
892
|
-
// Load from package assets first, then project .opencode (project overrides package)
|
|
893
|
-
const loadedAgents = loadAgentAssets(assetsDir, projectOpencodeDir);
|
|
894
836
|
// ==========================================================================
|
|
895
|
-
//
|
|
896
|
-
//
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
const { createModelRegistry } = await import('../shared/model-registry.js');
|
|
900
|
-
const runtimeConfig = loadRuntimeConfig(directory);
|
|
901
|
-
if (runtimeConfig.dummyHumans) {
|
|
902
|
-
const pool = normalizeDummyHumansConfig(runtimeConfig.dummyHumans);
|
|
903
|
-
// Initialize the global model registry
|
|
904
|
-
createModelRegistry(pool);
|
|
905
|
-
if (loadedAgents['dummy-human']) {
|
|
906
|
-
// Generate agent variants from pool entries
|
|
907
|
-
for (const entry of pool) {
|
|
908
|
-
const agentName = `dummy-${entry.id}`;
|
|
909
|
-
if (!loadedAgents[agentName]) {
|
|
910
|
-
const tierLabel = entry.tier === 'flash' ? 'Flash' : entry.tier === 'premium' ? 'Premium' : 'Standard';
|
|
911
|
-
const capStr = entry.capabilities.slice(0, 3).join(', ');
|
|
912
|
-
loadedAgents[agentName] = {
|
|
913
|
-
...loadedAgents['dummy-human'],
|
|
914
|
-
description: `Dummy-Human (${entry.id}) - ${tierLabel}. ${entry.description || capStr}. [max ${entry.maxConcurrent} concurrent]`,
|
|
915
|
-
model: entry.model,
|
|
916
|
-
};
|
|
917
|
-
}
|
|
918
|
-
}
|
|
919
|
-
// Also ensure legacy dummy-flash / dummy-premium aliases exist (for backward compat)
|
|
920
|
-
const flashEntry = pool.find(e => e.tier === 'flash');
|
|
921
|
-
const humanEntry = pool.find(e => e.tier === 'human');
|
|
922
|
-
const premiumEntry = pool.find(e => e.tier === 'premium');
|
|
923
|
-
if (flashEntry && !loadedAgents['dummy-flash']) {
|
|
924
|
-
loadedAgents['dummy-flash'] = loadedAgents[`dummy-${flashEntry.id}`];
|
|
925
|
-
}
|
|
926
|
-
if (humanEntry) {
|
|
927
|
-
// dummy-human already exists as the base agent; just update its model from pool
|
|
928
|
-
loadedAgents['dummy-human'].model = humanEntry.model;
|
|
929
|
-
}
|
|
930
|
-
if (premiumEntry && !loadedAgents['dummy-premium']) {
|
|
931
|
-
loadedAgents['dummy-premium'] = loadedAgents[`dummy-${premiumEntry.id}`];
|
|
932
|
-
}
|
|
933
|
-
// Fallback: if no tier mapping found, use defaults
|
|
934
|
-
if (!loadedAgents['dummy-flash']) {
|
|
935
|
-
loadedAgents['dummy-flash'] = {
|
|
936
|
-
...loadedAgents['dummy-human'],
|
|
937
|
-
description: 'Dummy-Human (Flash) - Fast and cheap',
|
|
938
|
-
model: 'google/gemini-2.0-flash',
|
|
939
|
-
};
|
|
940
|
-
}
|
|
941
|
-
if (!loadedAgents['dummy-premium']) {
|
|
942
|
-
loadedAgents['dummy-premium'] = {
|
|
943
|
-
...loadedAgents['dummy-human'],
|
|
944
|
-
description: 'Dummy-Human (Premium) - Powerful and reasoning',
|
|
945
|
-
model: 'google/gemini-2.0-pro-exp-02-05',
|
|
946
|
-
};
|
|
947
|
-
}
|
|
948
|
-
}
|
|
949
|
-
}
|
|
950
|
-
else {
|
|
951
|
-
// No pool config → legacy hardcoded defaults
|
|
952
|
-
if (loadedAgents['dummy-human']) {
|
|
953
|
-
if (!loadedAgents['dummy-flash']) {
|
|
954
|
-
loadedAgents['dummy-flash'] = {
|
|
955
|
-
...loadedAgents['dummy-human'],
|
|
956
|
-
description: 'Dummy-Human (Flash) - Fast and cheap',
|
|
957
|
-
model: 'google/gemini-2.0-flash',
|
|
958
|
-
};
|
|
959
|
-
}
|
|
960
|
-
if (!loadedAgents['dummy-premium']) {
|
|
961
|
-
loadedAgents['dummy-premium'] = {
|
|
962
|
-
...loadedAgents['dummy-human'],
|
|
963
|
-
description: 'Dummy-Human (Premium) - Powerful and reasoning',
|
|
964
|
-
model: 'google/gemini-2.0-pro-exp-02-05',
|
|
965
|
-
};
|
|
966
|
-
}
|
|
967
|
-
}
|
|
968
|
-
}
|
|
969
|
-
}
|
|
970
|
-
// Apply config overrides to agents
|
|
971
|
-
for (const agentId of Object.keys(loadedAgents)) {
|
|
972
|
-
const override = getAgentOverride(pluginConfig, agentId);
|
|
973
|
-
if (override) {
|
|
974
|
-
if (override.model)
|
|
975
|
-
loadedAgents[agentId].model = override.model;
|
|
976
|
-
if (override.systemPrompt)
|
|
977
|
-
loadedAgents[agentId].prompt = override.systemPrompt;
|
|
978
|
-
}
|
|
979
|
-
}
|
|
980
|
-
// ==========================================================================
|
|
981
|
-
// 9. Return plugin hooks
|
|
837
|
+
// 9. Return plugin hooks (official OpenCode Hooks interface only)
|
|
838
|
+
// Note: Agents are registered via .opencode/agents/*.md files (installed by
|
|
839
|
+
// installAssets()), NOT via the plugin return. The Hooks type does not
|
|
840
|
+
// include 'agent' — OpenCode loads agents from the filesystem exclusively.
|
|
982
841
|
// ==========================================================================
|
|
983
842
|
return {
|
|
984
|
-
// Agent registration
|
|
985
|
-
agent: loadedAgents,
|
|
843
|
+
// Agent registration handled via .opencode/agents/*.md files (see installAssets)
|
|
986
844
|
// System prompt transform - inject active mask
|
|
987
845
|
'experimental.chat.system.transform': async (_input, output) => {
|
|
988
846
|
if (state?.activeMask) {
|
|
@@ -1000,40 +858,46 @@ ${buildRichPrompt(state.activeMask)}
|
|
|
1000
858
|
event: async ({ event }) => {
|
|
1001
859
|
// Session created - log available masks
|
|
1002
860
|
if (event.type === 'session.created') {
|
|
1003
|
-
|
|
861
|
+
pluginState.currentSessionID = getSessionId(event);
|
|
862
|
+
if (pluginState.maskLoader && verbose) {
|
|
1004
863
|
try {
|
|
1005
|
-
const masks = await
|
|
1006
|
-
const categories = await
|
|
1007
|
-
client.
|
|
1008
|
-
service: 'maskweaver',
|
|
1009
|
-
level: 'info',
|
|
1010
|
-
message: `Session started - ${masks.length} masks available across ${categories.length} categories`,
|
|
1011
|
-
});
|
|
864
|
+
const masks = await pluginState.maskLoader.listAll();
|
|
865
|
+
const categories = await pluginState.maskLoader.listCategories();
|
|
866
|
+
pluginLog(client, 'info', `Session started - ${masks.length} masks available across ${categories.length} categories`);
|
|
1012
867
|
}
|
|
1013
868
|
catch (_e) {
|
|
1014
869
|
// Ignore errors
|
|
1015
870
|
}
|
|
1016
871
|
}
|
|
1017
872
|
}
|
|
873
|
+
// Session idle - generation completed
|
|
874
|
+
if (event.type === 'session.idle') {
|
|
875
|
+
const idleSessionID = getSessionId(event);
|
|
876
|
+
const isCurrentSession = !idleSessionID ||
|
|
877
|
+
!pluginState.currentSessionID ||
|
|
878
|
+
idleSessionID === pluginState.currentSessionID;
|
|
879
|
+
if (isCurrentSession) {
|
|
880
|
+
playCompletionSound(pluginState.config);
|
|
881
|
+
}
|
|
882
|
+
}
|
|
1018
883
|
// Session deleted - cleanup
|
|
1019
884
|
if (event.type === 'session.deleted') {
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
885
|
+
const deletedSessionID = getSessionId(event);
|
|
886
|
+
if (!deletedSessionID || deletedSessionID === pluginState.currentSessionID) {
|
|
887
|
+
pluginState.currentSessionID = null;
|
|
888
|
+
}
|
|
889
|
+
if (verbose) {
|
|
890
|
+
const wasActive = pluginState.activeMask !== null;
|
|
891
|
+
pluginState.activeMask = null;
|
|
1023
892
|
if (wasActive) {
|
|
1024
|
-
client
|
|
1025
|
-
service: 'maskweaver',
|
|
1026
|
-
level: 'info',
|
|
1027
|
-
message: 'Session ended - active mask cleared',
|
|
1028
|
-
});
|
|
893
|
+
pluginLog(client, 'info', 'Session ended - active mask cleared');
|
|
1029
894
|
}
|
|
1030
895
|
}
|
|
1031
896
|
}
|
|
1032
897
|
},
|
|
1033
|
-
// Config hook -
|
|
898
|
+
// Config hook - allows plugins to modify opencode configuration
|
|
1034
899
|
config: async (config) => {
|
|
1035
|
-
//
|
|
1036
|
-
// Agent overrides are currently not supported via this hook in opencode core.
|
|
900
|
+
// Reserved for future configuration injection (model, provider, etc.)
|
|
1037
901
|
return;
|
|
1038
902
|
},
|
|
1039
903
|
};
|