memorix 1.8.5 → 1.8.6
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 +18 -0
- package/README.md +4 -2
- package/assets/star-history-dark.svg +5 -5
- package/assets/star-history-light.svg +5 -5
- package/assets/star-history.json +35 -5
- package/dist/cli/index.js +309 -131
- package/dist/index.js +75 -11
- package/dist/memcode-runtime/CHANGELOG.md +18 -0
- package/dist/sdk.js +74 -10
- package/dist/types.d.ts +1 -1
- package/docs/API_REFERENCE.md +1 -1
- package/docs/DEVELOPMENT.md +2 -2
- package/docs/INTEGRATIONS.md +1 -0
- package/docs/MODULES.md +1 -1
- package/docs/README.md +3 -3
- package/docs/SETUP.md +1 -0
- package/package.json +1 -1
- package/plugins/antigravity/memorix/plugin.json +1 -1
- package/plugins/claude/memorix/.claude-plugin/plugin.json +1 -1
- package/plugins/codebuddy/memorix/.codebuddy-plugin/plugin.json +1 -1
- package/plugins/codex/memorix/.codex-plugin/plugin.json +1 -1
- package/plugins/copilot/memorix/plugin.json +1 -1
- package/plugins/gemini/memorix/gemini-extension.json +1 -1
- package/plugins/hermes/memorix/plugin.yaml +1 -1
- package/plugins/omp/memorix/package.json +1 -1
- package/plugins/openclaw/memorix/.codex-plugin/plugin.json +1 -1
- package/plugins/pi/memorix/package.json +1 -1
- package/server.json +2 -2
- package/src/cli/commands/agent-integrations.ts +1 -0
- package/src/cli/commands/hooks-install.ts +9 -2
- package/src/cli/commands/hooks-preview.ts +5 -0
- package/src/cli/commands/integrate.ts +2 -0
- package/src/cli/commands/setup.ts +11 -1
- package/src/cli/commands/uninstall.ts +24 -2
- package/src/codegraph/task-lens.ts +72 -7
- package/src/hooks/installers/index.ts +55 -0
- package/src/hooks/types.ts +2 -0
- package/src/integrations/registry.ts +9 -0
- package/src/types.ts +1 -1
- package/src/workspace/mcp-adapters/workbuddy.ts +71 -0
|
@@ -153,6 +153,7 @@ function estimateFileSize(agent: string, configPath: string): string {
|
|
|
153
153
|
opencode: '~3KB',
|
|
154
154
|
trae: '~1KB',
|
|
155
155
|
dsh: '~1KB',
|
|
156
|
+
workbuddy: '~1KB',
|
|
156
157
|
};
|
|
157
158
|
return sizes[agent] || '~2KB';
|
|
158
159
|
}
|
|
@@ -174,6 +175,8 @@ function getRulesPath(agent: string, cwd: string, global: boolean): string {
|
|
|
174
175
|
return path.join(home, '.trae', 'rules', 'project_rules.md');
|
|
175
176
|
case 'dsh':
|
|
176
177
|
return path.join(process.env.DSH_HOME?.trim() || path.join(home, '.dsh'), 'AGENTS.md');
|
|
178
|
+
case 'workbuddy':
|
|
179
|
+
return '';
|
|
177
180
|
default:
|
|
178
181
|
return '';
|
|
179
182
|
}
|
|
@@ -191,6 +194,8 @@ function getRulesPath(agent: string, cwd: string, global: boolean): string {
|
|
|
191
194
|
return path.join(cwd, '.trae', 'rules', 'project_rules.md');
|
|
192
195
|
case 'dsh':
|
|
193
196
|
return path.join(cwd, 'AGENTS.md');
|
|
197
|
+
case 'workbuddy':
|
|
198
|
+
return path.join(cwd, 'AGENTS.md');
|
|
194
199
|
default:
|
|
195
200
|
return '';
|
|
196
201
|
}
|
|
@@ -115,6 +115,7 @@ function getAgentLabel(agent: string): string {
|
|
|
115
115
|
'gemini-cli': 'Gemini CLI',
|
|
116
116
|
trae: 'Trae',
|
|
117
117
|
dsh: 'DeepSeek Harness',
|
|
118
|
+
workbuddy: 'WorkBuddy',
|
|
118
119
|
};
|
|
119
120
|
return labels[agent] || agent;
|
|
120
121
|
}
|
|
@@ -132,6 +133,7 @@ function getAgentHint(agent: string): string {
|
|
|
132
133
|
'gemini-cli': 'settings + GEMINI.md',
|
|
133
134
|
trae: 'rules',
|
|
134
135
|
dsh: 'MCP patch + AGENTS.md + skills',
|
|
136
|
+
workbuddy: 'MCP + AGENTS.md',
|
|
135
137
|
};
|
|
136
138
|
return hints[agent] || 'integration files';
|
|
137
139
|
}
|
|
@@ -31,6 +31,7 @@ import { KiroMCPAdapter } from '../../workspace/mcp-adapters/kiro.js';
|
|
|
31
31
|
import { OpenCodeMCPAdapter } from '../../workspace/mcp-adapters/opencode.js';
|
|
32
32
|
import { TraeMCPAdapter } from '../../workspace/mcp-adapters/trae.js';
|
|
33
33
|
import { DshMCPAdapter } from '../../workspace/mcp-adapters/dsh.js';
|
|
34
|
+
import { WorkbuddyMCPAdapter } from '../../workspace/mcp-adapters/workbuddy.js';
|
|
34
35
|
import { CodeBuddyMCPAdapter } from '../../workspace/mcp-adapters/codebuddy.js';
|
|
35
36
|
import { getSetupIntegrationRows as readSetupIntegrationRows } from '../../integrations/registry.js';
|
|
36
37
|
import { OFFICIAL_MEMORIX_SKILLS } from '../../hooks/official-skills.js';
|
|
@@ -192,6 +193,7 @@ const SUPPORTED_SETUP_AGENTS: AgentName[] = [
|
|
|
192
193
|
'kiro',
|
|
193
194
|
'trae',
|
|
194
195
|
'dsh',
|
|
196
|
+
'workbuddy',
|
|
195
197
|
'pi',
|
|
196
198
|
];
|
|
197
199
|
|
|
@@ -405,6 +407,7 @@ export function getMcpAdapter(agent: McpConfigAgent): MCPConfigAdapter {
|
|
|
405
407
|
opencode: new OpenCodeMCPAdapter(),
|
|
406
408
|
trae: new TraeMCPAdapter(),
|
|
407
409
|
dsh: new DshMCPAdapter(),
|
|
410
|
+
workbuddy: new WorkbuddyMCPAdapter(),
|
|
408
411
|
};
|
|
409
412
|
return adapters[agent];
|
|
410
413
|
}
|
|
@@ -1611,7 +1614,14 @@ export async function installAgentSetup(agent: AgentName, plan: SetupPlan, globa
|
|
|
1611
1614
|
}
|
|
1612
1615
|
} else if (plan.actions.includes('hooks')) {
|
|
1613
1616
|
const result = await installHooks(agent, targetRoot, global);
|
|
1614
|
-
|
|
1617
|
+
const note = (result.generated as Record<string, unknown>)?.note;
|
|
1618
|
+
if (result.configPath) {
|
|
1619
|
+
p.log.success(`${agent}: integration files -> ${result.configPath}`);
|
|
1620
|
+
} else if (note) {
|
|
1621
|
+
p.log.info(`${agent}: ${note}`);
|
|
1622
|
+
} else {
|
|
1623
|
+
p.log.info(`${agent}: no hooks or guidance are available at this scope`);
|
|
1624
|
+
}
|
|
1615
1625
|
} else if (plan.actions.includes('project-guidance')) {
|
|
1616
1626
|
const rulesPath = await installAgentGuidance(agent, targetRoot, global);
|
|
1617
1627
|
p.log.success(`${agent}: guidance -> ${rulesPath}`);
|
|
@@ -24,7 +24,7 @@ import * as os from 'node:os';
|
|
|
24
24
|
|
|
25
25
|
// ── MCP config paths (documented, not modified) ────────────────────
|
|
26
26
|
|
|
27
|
-
interface MCPConfigEntry {
|
|
27
|
+
export interface MCPConfigEntry {
|
|
28
28
|
agent: string;
|
|
29
29
|
path: string;
|
|
30
30
|
kind: 'project' | 'global';
|
|
@@ -66,7 +66,7 @@ function checkDshPatch(p: string): boolean {
|
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
/** Detects memorix MCP config entries in both global and project-level paths. */
|
|
69
|
-
function getMCPConfigEntries(home: string, cwd: string): MCPConfigEntry[] {
|
|
69
|
+
export function getMCPConfigEntries(home: string, cwd: string): MCPConfigEntry[] {
|
|
70
70
|
const entries: MCPConfigEntry[] = [];
|
|
71
71
|
|
|
72
72
|
// ── Global paths ──
|
|
@@ -236,6 +236,28 @@ function getMCPConfigEntries(home: string, cwd: string): MCPConfigEntry[] {
|
|
|
236
236
|
note: 'Remove the memory-memorix insert row from this patch file.',
|
|
237
237
|
});
|
|
238
238
|
|
|
239
|
+
// WorkBuddy — global
|
|
240
|
+
const workbuddyCfg = path.join(home, '.workbuddy', 'mcp.json');
|
|
241
|
+
entries.push({
|
|
242
|
+
agent: 'WorkBuddy',
|
|
243
|
+
path: workbuddyCfg,
|
|
244
|
+
kind: 'global',
|
|
245
|
+
format: 'json',
|
|
246
|
+
detected: checkJSON(workbuddyCfg),
|
|
247
|
+
note: 'Remove the "memorix" key from mcpServers in this file.',
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
// WorkBuddy — project level (project-only installs must be discoverable too)
|
|
251
|
+
const workbuddyProjectCfg = path.join(cwd, '.workbuddy', 'mcp.json');
|
|
252
|
+
entries.push({
|
|
253
|
+
agent: 'WorkBuddy',
|
|
254
|
+
path: workbuddyProjectCfg,
|
|
255
|
+
kind: 'project',
|
|
256
|
+
format: 'json',
|
|
257
|
+
detected: checkJSON(workbuddyProjectCfg),
|
|
258
|
+
note: 'Remove the "memorix" key from mcpServers in this file.',
|
|
259
|
+
});
|
|
260
|
+
|
|
239
261
|
return entries;
|
|
240
262
|
}
|
|
241
263
|
|
|
@@ -143,19 +143,73 @@ const KEYWORDS: Record<Exclude<TaskLensId, 'general'>, string[]> = {
|
|
|
143
143
|
// Continuation is a delivery intent, not a task lens. A request can both resume
|
|
144
144
|
// prior work and be a bugfix, feature, or release task, so callers keep the
|
|
145
145
|
// normal lens and add a bounded prior-work projection separately.
|
|
146
|
-
|
|
146
|
+
//
|
|
147
|
+
// Matching triggers buildHookProjectContext() and a full prior-work brief,
|
|
148
|
+
// which measurably costs 2-3x an ordinary prompt (~3s → ~7.5s locally, 15s+
|
|
149
|
+
// under load) — enough to blow the 10s UserPromptSubmit hook timeout, after
|
|
150
|
+
// which Claude Code discards the injection entirely.
|
|
151
|
+
//
|
|
152
|
+
// The split below is about *evidence of intent*, not about the words alone:
|
|
153
|
+
//
|
|
154
|
+
// EXPLICIT — handoff vocabulary is deliberate on its own. "交接" and
|
|
155
|
+
// "handoff" are not said casually, so a bare mention counts.
|
|
156
|
+
// AMBIGUOUS — "继续" / "continue" / "恢复" are everyday verbs. Bare, they
|
|
157
|
+
// are filler meaning "go on" (and "恢复" usually means "restore
|
|
158
|
+
// the service" in ops work). Carrying a real task they are a
|
|
159
|
+
// genuine resume request — `memorix resume "继续处理发布阻塞问题"`
|
|
160
|
+
// is documented usage — so they require substantive content.
|
|
161
|
+
const EXPLICIT_HANDOFF_KEYWORDS = [
|
|
162
|
+
'handoff',
|
|
163
|
+
'hand off',
|
|
164
|
+
'hand over',
|
|
165
|
+
'previous session',
|
|
166
|
+
'last session',
|
|
167
|
+
'pick up where',
|
|
168
|
+
'交接', // 覆盖「任务交接」「工作交接」
|
|
169
|
+
'接手',
|
|
170
|
+
'上次会话',
|
|
171
|
+
'上次的会话',
|
|
172
|
+
];
|
|
173
|
+
|
|
174
|
+
const AMBIGUOUS_CONTINUATION_KEYWORDS = [
|
|
147
175
|
'continue',
|
|
148
176
|
'resume',
|
|
149
|
-
'pick up',
|
|
150
177
|
'carry on',
|
|
151
|
-
'
|
|
178
|
+
'pick up',
|
|
152
179
|
'继续',
|
|
153
|
-
'接手',
|
|
154
180
|
'恢复',
|
|
155
181
|
'延续',
|
|
156
|
-
'上次会话',
|
|
157
182
|
];
|
|
158
183
|
|
|
184
|
+
/**
|
|
185
|
+
* Characters that carry no task meaning on their own: punctuation, whitespace,
|
|
186
|
+
* Chinese modal particles, and the generic scaffolding words users wrap around
|
|
187
|
+
* a bare "继续" ("任务继续", "继续一下").
|
|
188
|
+
*/
|
|
189
|
+
const TASK_FILLER_WORDS = /\b(?:please|task|work|the|a|an)\b/giu;
|
|
190
|
+
const TASK_FILLER_PHRASES = /(?:吧|了|呢|啊|吗|嘛|呀|哦|噢|一下|一起|接着|请|帮我)/gu;
|
|
191
|
+
|
|
192
|
+
/** Minimum residual code points that count as a real task description. */
|
|
193
|
+
const MIN_TASK_SUBSTANCE = 2;
|
|
194
|
+
|
|
195
|
+
function hasSubstantiveTask(text: string, keyword: string): boolean {
|
|
196
|
+
const withoutKeyword = text.replace(
|
|
197
|
+
new RegExp(keyword.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'gi'),
|
|
198
|
+
' ',
|
|
199
|
+
);
|
|
200
|
+
const residual = withoutKeyword
|
|
201
|
+
// English fillers are removed as whole words, so `a` in `API` and `work`
|
|
202
|
+
// in `workflow` remain meaningful task text.
|
|
203
|
+
.replace(TASK_FILLER_WORDS, ' ')
|
|
204
|
+
.replace(TASK_FILLER_PHRASES, ' ')
|
|
205
|
+
.replace(/[\s\p{P}]/gu, '');
|
|
206
|
+
|
|
207
|
+
// These are scaffolding-only phrases, not tasks. Keep `工作流` intact by
|
|
208
|
+
// checking the complete residual instead of removing `工作` as a prefix.
|
|
209
|
+
if (/^(?:任务|工作|一下|一起|接着)$/u.test(residual)) return false;
|
|
210
|
+
return Array.from(residual).length >= MIN_TASK_SUBSTANCE;
|
|
211
|
+
}
|
|
212
|
+
|
|
159
213
|
const LENS_PRIORITY: Exclude<TaskLensId, 'general'>[] = [
|
|
160
214
|
'bugfix',
|
|
161
215
|
'release',
|
|
@@ -250,11 +304,22 @@ export function resolveTaskLens(task?: string): TaskLens {
|
|
|
250
304
|
* Detect when the caller is asking to continue existing work. This stays
|
|
251
305
|
* separate from task-lens routing so "continue fixing the timeout" remains a
|
|
252
306
|
* bugfix, while still receiving a compact prior-work brief.
|
|
307
|
+
*
|
|
308
|
+
* Explicit handoff vocabulary counts on its own. Everyday continuation verbs
|
|
309
|
+
* additionally require a substantive task, so a bare "继续" / "continue" — the
|
|
310
|
+
* single most common thing a user types to mean "go on" — stays on the cheap
|
|
311
|
+
* path instead of paying for a full brief on every turn.
|
|
253
312
|
*/
|
|
254
313
|
export function isContinuationTask(task?: string): boolean {
|
|
255
314
|
const normalized = (task ?? '').trim().toLowerCase();
|
|
256
|
-
|
|
257
|
-
|
|
315
|
+
if (normalized.length === 0) return false;
|
|
316
|
+
|
|
317
|
+
if (EXPLICIT_HANDOFF_KEYWORDS.some((keyword) => containsTaskKeyword(normalized, keyword))) {
|
|
318
|
+
return true;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
return AMBIGUOUS_CONTINUATION_KEYWORDS.some((keyword) =>
|
|
322
|
+
containsTaskKeyword(normalized, keyword) && hasSubstantiveTask(normalized, keyword),
|
|
258
323
|
);
|
|
259
324
|
}
|
|
260
325
|
|
|
@@ -652,6 +652,12 @@ export function getProjectConfigPath(agent: AgentName, projectRoot: string): str
|
|
|
652
652
|
return path.join(projectRoot, '.agents', 'hooks.json');
|
|
653
653
|
case 'gemini-cli':
|
|
654
654
|
return path.join(projectRoot, '.gemini', 'settings.json');
|
|
655
|
+
case 'workbuddy':
|
|
656
|
+
// WorkBuddy has no hook system at all — only MCP config and project
|
|
657
|
+
// AGENTS.md guidance. Return an empty path so install, detection and
|
|
658
|
+
// uninstall all treat hooks as a no-op instead of falling through to the
|
|
659
|
+
// `.memorix/hooks.json` default (which would report a path that never exists).
|
|
660
|
+
return '';
|
|
655
661
|
default:
|
|
656
662
|
return path.join(projectRoot, '.memorix', 'hooks.json');
|
|
657
663
|
}
|
|
@@ -697,6 +703,9 @@ export function getGlobalConfigPath(agent: AgentName): string {
|
|
|
697
703
|
case 'dsh':
|
|
698
704
|
// DSH reads the user-global AGENTS.md from its harness home.
|
|
699
705
|
return path.join(process.env.DSH_HOME?.trim() || path.join(home, '.dsh'), 'AGENTS.md');
|
|
706
|
+
case 'workbuddy':
|
|
707
|
+
// WorkBuddy has no user-global instructions file — guidance is project-only.
|
|
708
|
+
return '';
|
|
700
709
|
default:
|
|
701
710
|
return path.join(home, '.memorix', 'hooks.json');
|
|
702
711
|
}
|
|
@@ -725,6 +734,9 @@ export function getAgentRulesPath(agent: AgentName, root: string, global = false
|
|
|
725
734
|
case 'dsh':
|
|
726
735
|
// The user-global AGENTS.md lives in the harness home DSH reads.
|
|
727
736
|
return path.join(process.env.DSH_HOME?.trim() || path.join(root, '.dsh'), 'AGENTS.md');
|
|
737
|
+
case 'workbuddy':
|
|
738
|
+
// WorkBuddy has no user-global instructions file — guidance is project-only.
|
|
739
|
+
return '';
|
|
728
740
|
default:
|
|
729
741
|
return path.join(root, '.agent', 'rules', 'memorix.md');
|
|
730
742
|
}
|
|
@@ -752,6 +764,8 @@ export function getAgentRulesPath(agent: AgentName, root: string, global = false
|
|
|
752
764
|
return path.join(root, '.trae', 'rules', 'project_rules.md');
|
|
753
765
|
case 'dsh':
|
|
754
766
|
return path.join(root, 'AGENTS.md');
|
|
767
|
+
case 'workbuddy':
|
|
768
|
+
return path.join(root, 'AGENTS.md');
|
|
755
769
|
default:
|
|
756
770
|
return path.join(root, '.agent', 'rules', 'memorix.md');
|
|
757
771
|
}
|
|
@@ -867,6 +881,12 @@ export async function detectInstalledAgents(): Promise<AgentName[]> {
|
|
|
867
881
|
agents.push('dsh');
|
|
868
882
|
} catch { /* not installed */ }
|
|
869
883
|
|
|
884
|
+
// Check for WorkBuddy (its user-level home dir ~/.workbuddy)
|
|
885
|
+
try {
|
|
886
|
+
await fs.access(path.join(home, '.workbuddy'));
|
|
887
|
+
agents.push('workbuddy');
|
|
888
|
+
} catch { /* not installed */ }
|
|
889
|
+
|
|
870
890
|
return agents;
|
|
871
891
|
}
|
|
872
892
|
|
|
@@ -889,6 +909,19 @@ export async function installHooks(
|
|
|
889
909
|
};
|
|
890
910
|
}
|
|
891
911
|
|
|
912
|
+
// WorkBuddy has no hook system and no user-global guidance file. Report an
|
|
913
|
+
// explicit no-op instead of a project path that is never written.
|
|
914
|
+
if (agent === 'workbuddy' && global) {
|
|
915
|
+
return {
|
|
916
|
+
agent,
|
|
917
|
+
configPath: '',
|
|
918
|
+
events: [],
|
|
919
|
+
generated: {
|
|
920
|
+
note: 'WorkBuddy has no hook system and no user-global guidance file — MCP only; no global guidance/hooks. Run `memorix setup --agent workbuddy` (without --global) to install project MCP config and AGENTS.md guidance.',
|
|
921
|
+
},
|
|
922
|
+
};
|
|
923
|
+
}
|
|
924
|
+
|
|
892
925
|
// Guard: reject global install for agents that don't support it
|
|
893
926
|
if (global && getGlobalConfigPath(agent) === '') {
|
|
894
927
|
return {
|
|
@@ -988,6 +1021,20 @@ export async function installHooks(
|
|
|
988
1021
|
},
|
|
989
1022
|
};
|
|
990
1023
|
}
|
|
1024
|
+
case 'workbuddy':
|
|
1025
|
+
// WorkBuddy has no hook system — install AGENTS.md guidance only;
|
|
1026
|
+
// the MCP row is installed by `memorix setup --agent workbuddy`.
|
|
1027
|
+
{
|
|
1028
|
+
const rulesPath = await installAgentRules(agent, projectRoot, global);
|
|
1029
|
+
return {
|
|
1030
|
+
agent,
|
|
1031
|
+
configPath: rulesPath,
|
|
1032
|
+
events: [],
|
|
1033
|
+
generated: {
|
|
1034
|
+
note: 'WorkBuddy has no hook system — installed AGENTS.md guidance only; the MCP row is installed by `memorix setup --agent workbuddy`.',
|
|
1035
|
+
},
|
|
1036
|
+
};
|
|
1037
|
+
}
|
|
991
1038
|
case 'opencode': {
|
|
992
1039
|
// OpenCode uses JS plugin files for hooks
|
|
993
1040
|
const pluginContent = generateOpenCodePlugin();
|
|
@@ -1161,6 +1208,8 @@ export async function installHooks(
|
|
|
1161
1208
|
* Rules instruct the agent to proactively use memorix for context continuity.
|
|
1162
1209
|
*/
|
|
1163
1210
|
async function installAgentRules(agent: AgentName, projectRoot: string, global = false): Promise<string> {
|
|
1211
|
+
// WorkBuddy has no user-global instructions file — guidance is project-only.
|
|
1212
|
+
if (agent === 'workbuddy' && global) return '';
|
|
1164
1213
|
const rulesContent = getAgentRulesContent(agent, global ? 'global' : 'project');
|
|
1165
1214
|
const rulesRoot = global ? os.homedir() : projectRoot;
|
|
1166
1215
|
const rulesPath = getAgentRulesPath(agent, rulesRoot, global);
|
|
@@ -1355,6 +1404,12 @@ export async function uninstallHooks(
|
|
|
1355
1404
|
return false;
|
|
1356
1405
|
}
|
|
1357
1406
|
|
|
1407
|
+
// WorkBuddy has no hook file at either scope — removing hooks is a no-op.
|
|
1408
|
+
// It must not touch the generic `.memorix/hooks.json` default path.
|
|
1409
|
+
if (agent === 'workbuddy') {
|
|
1410
|
+
return false;
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1358
1413
|
// Guard: reject global uninstall for agents that don't support it
|
|
1359
1414
|
if (global && getGlobalConfigPath(agent) === '') {
|
|
1360
1415
|
return false;
|
package/src/hooks/types.ts
CHANGED
|
@@ -42,6 +42,7 @@ export type AgentName =
|
|
|
42
42
|
| 'opencode'
|
|
43
43
|
| 'trae'
|
|
44
44
|
| 'dsh'
|
|
45
|
+
| 'workbuddy'
|
|
45
46
|
| 'pi'
|
|
46
47
|
| 'omp';
|
|
47
48
|
|
|
@@ -63,6 +64,7 @@ export const AGENT_SUPPORT_TIER: Record<AgentName, SupportTier> = {
|
|
|
63
64
|
opencode: 'community',
|
|
64
65
|
trae: 'community',
|
|
65
66
|
dsh: 'community',
|
|
67
|
+
workbuddy: 'community',
|
|
66
68
|
pi: 'community',
|
|
67
69
|
omp: 'community',
|
|
68
70
|
antigravity: 'community',
|
|
@@ -159,6 +159,15 @@ export const MEMORIX_INTEGRATIONS: MemorixIntegration[] = [
|
|
|
159
159
|
surfaces: ['MCP', 'AGENTS.md', 'skills'],
|
|
160
160
|
note: 'DeepSeek Harness support writes a @deepseek-ai/dsh-mcp-client row into $DSH_HOME/cordis.patch.yml (default ~/.dsh/cordis.patch.yml), guidance into the harness AGENTS.md, and skills under $DSH_HOME/skills.',
|
|
161
161
|
},
|
|
162
|
+
{
|
|
163
|
+
agent: 'workbuddy',
|
|
164
|
+
name: 'WorkBuddy',
|
|
165
|
+
entry: 'official-config',
|
|
166
|
+
status: 'ready',
|
|
167
|
+
install: 'memorix setup --agent workbuddy',
|
|
168
|
+
surfaces: ['MCP', 'AGENTS.md'],
|
|
169
|
+
note: 'WorkBuddy support writes a memorix MCP server row into ~/.workbuddy/mcp.json (or <project>/.workbuddy/mcp.json for project installs) and guidance into AGENTS.md.',
|
|
170
|
+
},
|
|
162
171
|
{
|
|
163
172
|
agent: 'memcode',
|
|
164
173
|
name: 'memcode',
|
package/src/types.ts
CHANGED
|
@@ -670,7 +670,7 @@ export interface MemoryRef {
|
|
|
670
670
|
/** MCP config format adapter interface */
|
|
671
671
|
export interface MCPConfigAdapter {
|
|
672
672
|
/** Setup-only adapters may support a host that does not participate in workspace sync. */
|
|
673
|
-
readonly source: AgentTarget | 'codebuddy' | 'dsh';
|
|
673
|
+
readonly source: AgentTarget | 'codebuddy' | 'dsh' | 'workbuddy';
|
|
674
674
|
/** Parse MCP server entries from a config file */
|
|
675
675
|
parse(content: string): MCPServerEntry[];
|
|
676
676
|
/** Generate config file content from MCP server entries */
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type { MCPConfigAdapter, MCPServerEntry } from '../../types.js';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* WorkBuddy MCP config adapter.
|
|
7
|
+
*
|
|
8
|
+
* WorkBuddy stores its MCP server list in a JSON file following the standard
|
|
9
|
+
* MCP client convention:
|
|
10
|
+
*
|
|
11
|
+
* { "mcpServers": { [name]: { command, args, env?, url?, headers? } } }
|
|
12
|
+
*
|
|
13
|
+
* Per the official WorkBuddy MCP guide, two scopes are supported:
|
|
14
|
+
* - user-level: ~/.workbuddy/mcp.json
|
|
15
|
+
* - project-level: <project>/.workbuddy/mcp.json
|
|
16
|
+
*
|
|
17
|
+
* getConfigPath returns the project-level path when a projectRoot is supplied,
|
|
18
|
+
* otherwise the user-level path — matching the existing adapter convention.
|
|
19
|
+
*/
|
|
20
|
+
export class WorkbuddyMCPAdapter implements MCPConfigAdapter {
|
|
21
|
+
readonly source = 'workbuddy' as const;
|
|
22
|
+
|
|
23
|
+
parse(content: string): MCPServerEntry[] {
|
|
24
|
+
try {
|
|
25
|
+
const config = JSON.parse(content);
|
|
26
|
+
const servers = config.mcpServers ?? {};
|
|
27
|
+
return Object.entries(servers).map(([name, entry]: [string, any]) => ({
|
|
28
|
+
name,
|
|
29
|
+
command: entry.command ?? '',
|
|
30
|
+
args: entry.args ?? [],
|
|
31
|
+
...(entry.env && Object.keys(entry.env).length > 0 ? { env: entry.env } : {}),
|
|
32
|
+
...(entry.url ? { url: entry.url } : {}),
|
|
33
|
+
...(entry.headers && Object.keys(entry.headers).length > 0 ? { headers: entry.headers } : {}),
|
|
34
|
+
...(entry.disabled === true ? { disabled: true } : {}),
|
|
35
|
+
}));
|
|
36
|
+
} catch {
|
|
37
|
+
return [];
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
generate(servers: MCPServerEntry[]): string {
|
|
42
|
+
const mcpServers: Record<string, any> = {};
|
|
43
|
+
for (const s of servers) {
|
|
44
|
+
const entry: Record<string, any> = {};
|
|
45
|
+
if (s.url) {
|
|
46
|
+
entry.url = s.url;
|
|
47
|
+
if (s.headers && Object.keys(s.headers).length > 0) {
|
|
48
|
+
entry.headers = s.headers;
|
|
49
|
+
}
|
|
50
|
+
} else {
|
|
51
|
+
entry.command = s.command;
|
|
52
|
+
entry.args = s.args;
|
|
53
|
+
}
|
|
54
|
+
if (s.env && Object.keys(s.env).length > 0) {
|
|
55
|
+
entry.env = s.env;
|
|
56
|
+
}
|
|
57
|
+
if (s.disabled === true) {
|
|
58
|
+
entry.disabled = true;
|
|
59
|
+
}
|
|
60
|
+
mcpServers[s.name] = entry;
|
|
61
|
+
}
|
|
62
|
+
return JSON.stringify({ mcpServers }, null, 2) + '\n';
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
getConfigPath(projectRoot?: string): string {
|
|
66
|
+
if (projectRoot) {
|
|
67
|
+
return join(projectRoot, '.workbuddy', 'mcp.json');
|
|
68
|
+
}
|
|
69
|
+
return join(homedir(), '.workbuddy', 'mcp.json');
|
|
70
|
+
}
|
|
71
|
+
}
|