@zhijiewang/openharness 2.0.0 → 2.3.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/README.md +4 -4
- package/dist/DeferredTool.js +3 -1
- package/dist/Tool.d.ts +1 -1
- package/dist/agents/roles.js +58 -62
- package/dist/commands/cybergotchi.d.ts +1 -1
- package/dist/commands/cybergotchi.js +30 -30
- package/dist/commands/index.js +360 -122
- package/dist/components/App.d.ts +1 -1
- package/dist/components/App.js +6 -6
- package/dist/components/CompanionFooter.d.ts +1 -1
- package/dist/components/CompanionFooter.js +6 -8
- package/dist/components/CybergotchiBubble.js +5 -5
- package/dist/components/CybergotchiPanel.d.ts +1 -1
- package/dist/components/CybergotchiPanel.js +7 -7
- package/dist/components/CybergotchiPanelConnected.js +2 -2
- package/dist/components/CybergotchiSetup.js +26 -24
- package/dist/components/CybergotchiSprite.d.ts +1 -1
- package/dist/components/CybergotchiSprite.js +8 -12
- package/dist/components/DiffView.d.ts +1 -1
- package/dist/components/DiffView.js +10 -10
- package/dist/components/ErrorBoundary.d.ts +1 -1
- package/dist/components/ErrorBoundary.js +1 -1
- package/dist/components/InitWizard.js +65 -33
- package/dist/components/Markdown.js +2 -4
- package/dist/components/Messages.js +4 -4
- package/dist/components/PermissionPrompt.d.ts +1 -1
- package/dist/components/PermissionPrompt.js +15 -17
- package/dist/components/REPL.d.ts +1 -1
- package/dist/components/REPL.js +74 -49
- package/dist/components/Spinner.js +2 -2
- package/dist/components/TextInput.js +35 -29
- package/dist/components/ToolCallDisplay.js +3 -5
- package/dist/cybergotchi/bones.d.ts +1 -1
- package/dist/cybergotchi/bones.js +8 -8
- package/dist/cybergotchi/config.d.ts +2 -2
- package/dist/cybergotchi/config.js +13 -13
- package/dist/cybergotchi/events.d.ts +5 -5
- package/dist/cybergotchi/events.js +7 -7
- package/dist/cybergotchi/needs.d.ts +2 -2
- package/dist/cybergotchi/needs.js +7 -9
- package/dist/cybergotchi/personality.d.ts +2 -2
- package/dist/cybergotchi/personality.js +2 -2
- package/dist/cybergotchi/species.d.ts +1 -1
- package/dist/cybergotchi/species.js +145 -217
- package/dist/cybergotchi/speech.d.ts +2 -2
- package/dist/cybergotchi/speech.js +43 -43
- package/dist/cybergotchi/types.d.ts +4 -4
- package/dist/cybergotchi/types.js +26 -26
- package/dist/cybergotchi/useCybergotchi.d.ts +1 -1
- package/dist/cybergotchi/useCybergotchi.js +29 -25
- package/dist/git/index.js +11 -9
- package/dist/harness/checkpoints.js +29 -21
- package/dist/harness/config.d.ts +12 -2
- package/dist/harness/config.js +15 -9
- package/dist/harness/context-warning.d.ts +1 -1
- package/dist/harness/context-warning.js +1 -1
- package/dist/harness/cost.js +1 -1
- package/dist/harness/credentials.js +13 -13
- package/dist/harness/hooks.js +7 -5
- package/dist/harness/keybindings.js +20 -18
- package/dist/harness/marketplace.d.ts +3 -3
- package/dist/harness/marketplace.js +55 -42
- package/dist/harness/memory.d.ts +23 -5
- package/dist/harness/memory.js +142 -41
- package/dist/harness/onboarding.js +30 -10
- package/dist/harness/plugins.d.ts +9 -1
- package/dist/harness/plugins.js +54 -30
- package/dist/harness/rules.js +12 -7
- package/dist/harness/sandbox.d.ts +34 -0
- package/dist/harness/sandbox.js +104 -0
- package/dist/harness/session-db.d.ts +55 -0
- package/dist/harness/session-db.js +165 -0
- package/dist/harness/session.d.ts +1 -1
- package/dist/harness/session.js +34 -15
- package/dist/harness/store.d.ts +3 -3
- package/dist/harness/store.js +6 -4
- package/dist/harness/submit-handler.d.ts +4 -4
- package/dist/harness/submit-handler.js +57 -21
- package/dist/harness/telemetry.d.ts +1 -1
- package/dist/harness/telemetry.js +23 -19
- package/dist/harness/traces.d.ts +2 -2
- package/dist/harness/traces.js +44 -33
- package/dist/harness/verification.d.ts +1 -1
- package/dist/harness/verification.js +50 -44
- package/dist/lsp/client.js +44 -40
- package/dist/main.js +100 -59
- package/dist/mcp/DeferredMcpTool.d.ts +4 -4
- package/dist/mcp/DeferredMcpTool.js +9 -5
- package/dist/mcp/McpTool.d.ts +4 -4
- package/dist/mcp/McpTool.js +8 -4
- package/dist/mcp/client.d.ts +2 -2
- package/dist/mcp/client.js +21 -21
- package/dist/mcp/loader.d.ts +1 -1
- package/dist/mcp/loader.js +17 -12
- package/dist/mcp/registry.d.ts +3 -3
- package/dist/mcp/registry.js +97 -97
- package/dist/mcp/schema.d.ts +1 -1
- package/dist/mcp/schema.js +16 -16
- package/dist/mcp/server.d.ts +1 -1
- package/dist/mcp/server.js +21 -21
- package/dist/mcp/types.d.ts +3 -3
- package/dist/providers/anthropic.d.ts +2 -2
- package/dist/providers/anthropic.js +10 -9
- package/dist/providers/base.d.ts +1 -1
- package/dist/providers/index.js +10 -3
- package/dist/providers/llamacpp.d.ts +2 -2
- package/dist/providers/llamacpp.js +1 -3
- package/dist/providers/ollama.d.ts +2 -2
- package/dist/providers/ollama.js +3 -4
- package/dist/providers/openai.d.ts +2 -2
- package/dist/providers/openai.js +3 -5
- package/dist/providers/openrouter.d.ts +2 -2
- package/dist/providers/router.d.ts +1 -1
- package/dist/providers/router.js +7 -7
- package/dist/query/compress.d.ts +2 -2
- package/dist/query/compress.js +22 -21
- package/dist/query/context-manager.d.ts +2 -2
- package/dist/query/context-manager.js +8 -11
- package/dist/query/errors.js +1 -1
- package/dist/query/index.d.ts +1 -1
- package/dist/query/index.js +30 -22
- package/dist/query/tools.js +15 -12
- package/dist/query/types.d.ts +1 -1
- package/dist/query.d.ts +1 -1
- package/dist/query.js +1 -1
- package/dist/remote/auth.d.ts +2 -2
- package/dist/remote/auth.js +8 -8
- package/dist/remote/server.d.ts +3 -3
- package/dist/remote/server.js +60 -60
- package/dist/renderer/cells.js +9 -9
- package/dist/renderer/colors.js +24 -6
- package/dist/renderer/diff.d.ts +2 -2
- package/dist/renderer/diff.js +27 -19
- package/dist/renderer/differ.d.ts +1 -1
- package/dist/renderer/differ.js +9 -9
- package/dist/renderer/image.js +19 -19
- package/dist/renderer/index.d.ts +6 -6
- package/dist/renderer/index.js +163 -93
- package/dist/renderer/input.js +66 -48
- package/dist/renderer/layout.d.ts +6 -6
- package/dist/renderer/layout.js +163 -124
- package/dist/renderer/markdown.d.ts +2 -2
- package/dist/renderer/markdown.js +173 -54
- package/dist/renderer/session-browser.d.ts +2 -2
- package/dist/renderer/session-browser.js +19 -21
- package/dist/repl.d.ts +5 -5
- package/dist/repl.js +300 -198
- package/dist/sdk/index.d.ts +8 -7
- package/dist/sdk/index.js +59 -42
- package/dist/services/AgentDispatcher.d.ts +3 -3
- package/dist/services/AgentDispatcher.js +33 -29
- package/dist/services/CronExecutor.d.ts +4 -4
- package/dist/services/CronExecutor.js +12 -8
- package/dist/services/EvaluatorLoop.d.ts +3 -3
- package/dist/services/EvaluatorLoop.js +29 -21
- package/dist/services/MetaHarness.d.ts +1 -1
- package/dist/services/MetaHarness.js +41 -33
- package/dist/services/PipelineExecutor.d.ts +1 -1
- package/dist/services/PipelineExecutor.js +23 -25
- package/dist/services/SkillExtractor.d.ts +43 -0
- package/dist/services/SkillExtractor.js +143 -0
- package/dist/services/StreamingToolExecutor.d.ts +2 -2
- package/dist/services/StreamingToolExecutor.js +11 -7
- package/dist/services/a2a.d.ts +8 -8
- package/dist/services/a2a.js +44 -34
- package/dist/services/agent-messaging.d.ts +33 -15
- package/dist/services/agent-messaging.js +65 -13
- package/dist/services/cron.js +16 -16
- package/dist/tools/AgentTool/index.d.ts +5 -2
- package/dist/tools/AgentTool/index.js +35 -15
- package/dist/tools/AskUserTool/index.js +1 -1
- package/dist/tools/BashTool/index.d.ts +2 -2
- package/dist/tools/BashTool/index.js +18 -10
- package/dist/tools/CronTool/index.d.ts +2 -2
- package/dist/tools/CronTool/index.js +30 -12
- package/dist/tools/DiagnosticsTool/index.js +28 -22
- package/dist/tools/EnterPlanModeTool/index.js +93 -14
- package/dist/tools/EnterWorktreeTool/index.js +7 -3
- package/dist/tools/ExitPlanModeTool/index.d.ts +22 -1
- package/dist/tools/ExitPlanModeTool/index.js +20 -5
- package/dist/tools/ExitWorktreeTool/index.js +11 -4
- package/dist/tools/FileEditTool/index.js +3 -5
- package/dist/tools/FileReadTool/index.js +16 -10
- package/dist/tools/FileWriteTool/index.js +2 -2
- package/dist/tools/GlobTool/index.js +5 -9
- package/dist/tools/GrepTool/index.d.ts +2 -2
- package/dist/tools/GrepTool/index.js +14 -9
- package/dist/tools/ImageReadTool/index.js +2 -2
- package/dist/tools/KillProcessTool/index.js +11 -7
- package/dist/tools/LSTool/index.js +3 -3
- package/dist/tools/MemoryTool/index.d.ts +11 -11
- package/dist/tools/MemoryTool/index.js +28 -14
- package/dist/tools/MonitorTool/index.d.ts +2 -2
- package/dist/tools/MonitorTool/index.js +24 -19
- package/dist/tools/MultiEditTool/index.js +9 -5
- package/dist/tools/NotebookEditTool/index.js +3 -3
- package/dist/tools/ParallelAgentTool/index.d.ts +4 -4
- package/dist/tools/ParallelAgentTool/index.js +12 -6
- package/dist/tools/PipelineTool/index.d.ts +4 -4
- package/dist/tools/PipelineTool/index.js +3 -3
- package/dist/tools/PowerShellTool/index.js +10 -6
- package/dist/tools/RemoteTriggerTool/index.js +8 -4
- package/dist/tools/ScheduleWakeupTool/index.d.ts +42 -0
- package/dist/tools/ScheduleWakeupTool/index.js +115 -0
- package/dist/tools/SendMessageTool/index.js +25 -7
- package/dist/tools/SessionSearchTool/index.d.ts +15 -0
- package/dist/tools/SessionSearchTool/index.js +36 -0
- package/dist/tools/SkillTool/index.d.ts +3 -0
- package/dist/tools/SkillTool/index.js +39 -9
- package/dist/tools/TaskCreateTool/index.d.ts +2 -2
- package/dist/tools/TaskCreateTool/index.js +2 -2
- package/dist/tools/TaskGetTool/index.js +2 -2
- package/dist/tools/TaskListTool/index.js +3 -5
- package/dist/tools/TaskOutputTool/index.js +2 -2
- package/dist/tools/TaskStopTool/index.js +3 -3
- package/dist/tools/TaskUpdateTool/index.d.ts +4 -4
- package/dist/tools/TaskUpdateTool/index.js +2 -2
- package/dist/tools/ToolSearchTool/index.js +9 -6
- package/dist/tools/WebFetchTool/index.js +1 -1
- package/dist/tools/WebSearchTool/index.js +2 -6
- package/dist/tools.js +31 -30
- package/dist/types/permissions.js +15 -9
- package/dist/utils/bash-safety.d.ts +1 -1
- package/dist/utils/bash-safety.js +64 -54
- package/dist/utils/diff-algorithm.d.ts +3 -3
- package/dist/utils/diff-algorithm.js +7 -7
- package/dist/utils/fs.js +3 -3
- package/dist/utils/safe-env.js +1 -1
- package/dist/utils/theme-data.d.ts +1 -1
- package/dist/utils/theme-data.js +1 -1
- package/dist/utils/theme.d.ts +1 -1
- package/dist/utils/theme.js +1 -1
- package/dist/utils/tool-summary.d.ts +1 -1
- package/dist/utils/tool-summary.js +27 -9
- package/package.json +10 -3
package/dist/harness/memory.js
CHANGED
|
@@ -7,27 +7,29 @@
|
|
|
7
7
|
* The system detects learnable patterns from assistant responses and saves them
|
|
8
8
|
* without user intervention.
|
|
9
9
|
*/
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import { createUserMessage } from
|
|
14
|
-
const PROJECT_MEMORY_DIR = join(
|
|
15
|
-
const GLOBAL_MEMORY_DIR = join(homedir(),
|
|
10
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
|
|
11
|
+
import { homedir } from "node:os";
|
|
12
|
+
import { join, resolve, sep } from "node:path";
|
|
13
|
+
import { createUserMessage } from "../types/message.js";
|
|
14
|
+
const PROJECT_MEMORY_DIR = join(".oh", "memory");
|
|
15
|
+
const GLOBAL_MEMORY_DIR = join(homedir(), ".oh", "memory");
|
|
16
16
|
/** Load all memories from project and global dirs */
|
|
17
17
|
export function loadMemories() {
|
|
18
18
|
const entries = [];
|
|
19
19
|
for (const dir of [PROJECT_MEMORY_DIR, GLOBAL_MEMORY_DIR]) {
|
|
20
20
|
if (!existsSync(dir))
|
|
21
21
|
continue;
|
|
22
|
-
for (const file of readdirSync(dir).filter(f => f.endsWith(
|
|
22
|
+
for (const file of readdirSync(dir).filter((f) => f.endsWith(".md"))) {
|
|
23
23
|
try {
|
|
24
24
|
const filePath = join(dir, file);
|
|
25
|
-
const raw = readFileSync(filePath,
|
|
25
|
+
const raw = readFileSync(filePath, "utf-8");
|
|
26
26
|
const entry = parseMemory(raw, filePath);
|
|
27
27
|
if (entry)
|
|
28
28
|
entries.push(entry);
|
|
29
29
|
}
|
|
30
|
-
catch {
|
|
30
|
+
catch {
|
|
31
|
+
/* skip */
|
|
32
|
+
}
|
|
31
33
|
}
|
|
32
34
|
}
|
|
33
35
|
return entries;
|
|
@@ -40,36 +42,39 @@ function parseMemory(raw, filePath) {
|
|
|
40
42
|
if (!nameMatch)
|
|
41
43
|
return null;
|
|
42
44
|
// Content is everything after the frontmatter closing ---
|
|
43
|
-
const fmEnd = raw.indexOf(
|
|
44
|
-
const content = fmEnd > 0 ? raw.slice(fmEnd + 3).trim() :
|
|
45
|
+
const fmEnd = raw.indexOf("---", raw.indexOf("---") + 3);
|
|
46
|
+
const content = fmEnd > 0 ? raw.slice(fmEnd + 3).trim() : "";
|
|
45
47
|
const relevanceMatch = raw.match(/^relevance:\s*([0-9.]+)$/m);
|
|
46
48
|
const lastAccessedMatch = raw.match(/^lastAccessed:\s*(\d+)$/m);
|
|
47
49
|
const createdAtMatch = raw.match(/^createdAt:\s*(\d+)$/m);
|
|
48
50
|
const accessCountMatch = raw.match(/^accessCount:\s*(\d+)$/m);
|
|
49
51
|
return {
|
|
50
52
|
name: nameMatch[1].trim(),
|
|
51
|
-
type: (typeMatch?.[1]?.trim() ??
|
|
52
|
-
description: descMatch?.[1]?.trim() ??
|
|
53
|
+
type: (typeMatch?.[1]?.trim() ?? "user"),
|
|
54
|
+
description: descMatch?.[1]?.trim() ?? "",
|
|
53
55
|
content,
|
|
54
56
|
filePath,
|
|
55
57
|
relevance: relevanceMatch ? parseFloat(relevanceMatch[1]) : 0.5,
|
|
56
|
-
lastAccessed: lastAccessedMatch ? parseInt(lastAccessedMatch[1]) : undefined,
|
|
57
|
-
createdAt: createdAtMatch ? parseInt(createdAtMatch[1]) : undefined,
|
|
58
|
-
accessCount: accessCountMatch ? parseInt(accessCountMatch[1]) : 0,
|
|
58
|
+
lastAccessed: lastAccessedMatch ? parseInt(lastAccessedMatch[1], 10) : undefined,
|
|
59
|
+
createdAt: createdAtMatch ? parseInt(createdAtMatch[1], 10) : undefined,
|
|
60
|
+
accessCount: accessCountMatch ? parseInt(accessCountMatch[1], 10) : 0,
|
|
59
61
|
};
|
|
60
62
|
}
|
|
61
63
|
/** Build a system prompt section from loaded memories */
|
|
62
64
|
export function memoriesToPrompt(memories) {
|
|
63
65
|
if (memories.length === 0)
|
|
64
|
-
return
|
|
65
|
-
const lines = memories.map(m => `- **${m.name}** (${m.type}): ${m.content.slice(0, 200)}`);
|
|
66
|
-
return `# Remembered Context\n${lines.join(
|
|
66
|
+
return "";
|
|
67
|
+
const lines = memories.map((m) => `- **${m.name}** (${m.type}): ${m.content.slice(0, 200)}`);
|
|
68
|
+
return `# Remembered Context\n${lines.join("\n")}`;
|
|
67
69
|
}
|
|
68
70
|
/** Save a memory entry to the project memory directory */
|
|
69
71
|
export function saveMemory(name, type, description, content, global = false) {
|
|
70
72
|
const dir = global ? GLOBAL_MEMORY_DIR : PROJECT_MEMORY_DIR;
|
|
71
73
|
mkdirSync(dir, { recursive: true });
|
|
72
|
-
const slug = name
|
|
74
|
+
const slug = name
|
|
75
|
+
.toLowerCase()
|
|
76
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
77
|
+
.slice(0, 50);
|
|
73
78
|
const filePath = join(dir, `${slug}.md`);
|
|
74
79
|
const now = Date.now();
|
|
75
80
|
const md = `---
|
|
@@ -85,45 +90,95 @@ accessCount: 0
|
|
|
85
90
|
${content}
|
|
86
91
|
`;
|
|
87
92
|
writeFileSync(filePath, md);
|
|
93
|
+
updateMemoryIndex(dir);
|
|
88
94
|
return filePath;
|
|
89
95
|
}
|
|
96
|
+
/**
|
|
97
|
+
* Update or create MEMORY.md index file in the given memory directory.
|
|
98
|
+
* The index is always loaded into context, providing instant awareness of all stored memories.
|
|
99
|
+
* Each entry is a one-liner pointer to the individual memory file (~200 line cap).
|
|
100
|
+
*/
|
|
101
|
+
export function updateMemoryIndex(dir = PROJECT_MEMORY_DIR) {
|
|
102
|
+
if (!existsSync(dir))
|
|
103
|
+
return;
|
|
104
|
+
const files = readdirSync(dir).filter((f) => f.endsWith(".md") && f !== "MEMORY.md");
|
|
105
|
+
const entries = [];
|
|
106
|
+
for (const file of files) {
|
|
107
|
+
try {
|
|
108
|
+
const raw = readFileSync(join(dir, file), "utf-8");
|
|
109
|
+
const nameMatch = raw.match(/^name:\s*(.+)$/m);
|
|
110
|
+
const descMatch = raw.match(/^description:\s*(.+)$/m);
|
|
111
|
+
if (nameMatch) {
|
|
112
|
+
entries.push({
|
|
113
|
+
name: nameMatch[1].trim(),
|
|
114
|
+
file,
|
|
115
|
+
description: descMatch?.[1]?.trim() ?? "",
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
/* skip */
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
const lines = ["# Memory Index", ""];
|
|
124
|
+
for (const e of entries) {
|
|
125
|
+
// Keep each line under ~150 chars for readability
|
|
126
|
+
const hook = e.description.length > 100 ? `${e.description.slice(0, 97)}...` : e.description;
|
|
127
|
+
lines.push(`- [${e.name}](${e.file}) — ${hook}`);
|
|
128
|
+
}
|
|
129
|
+
lines.push("");
|
|
130
|
+
writeFileSync(join(dir, "MEMORY.md"), lines.join("\n"));
|
|
131
|
+
}
|
|
90
132
|
/** Mark a memory as accessed — updates lastAccessed and accessCount in the file */
|
|
91
133
|
export function touchMemory(entry) {
|
|
92
134
|
try {
|
|
93
|
-
let raw = readFileSync(entry.filePath,
|
|
135
|
+
let raw = readFileSync(entry.filePath, "utf-8");
|
|
94
136
|
const now = Date.now();
|
|
95
137
|
const newCount = (entry.accessCount ?? 0) + 1;
|
|
96
|
-
// Update or insert
|
|
138
|
+
// Update existing fields in frontmatter, or insert before closing ---
|
|
97
139
|
if (raw.match(/^lastAccessed:/m)) {
|
|
98
140
|
raw = raw.replace(/^lastAccessed:\s*\d+$/m, `lastAccessed: ${now}`);
|
|
99
141
|
}
|
|
100
142
|
else {
|
|
101
|
-
|
|
143
|
+
// Insert before the CLOSING --- (second occurrence)
|
|
144
|
+
const firstIdx = raw.indexOf("---");
|
|
145
|
+
const closingIdx = raw.indexOf("---", firstIdx + 3);
|
|
146
|
+
if (closingIdx > 0) {
|
|
147
|
+
raw = `${raw.slice(0, closingIdx)}lastAccessed: ${now}\n${raw.slice(closingIdx)}`;
|
|
148
|
+
}
|
|
102
149
|
}
|
|
103
150
|
if (raw.match(/^accessCount:/m)) {
|
|
104
151
|
raw = raw.replace(/^accessCount:\s*\d+$/m, `accessCount: ${newCount}`);
|
|
105
152
|
}
|
|
106
153
|
else {
|
|
107
|
-
|
|
154
|
+
const firstIdx = raw.indexOf("---");
|
|
155
|
+
const closingIdx = raw.indexOf("---", firstIdx + 3);
|
|
156
|
+
if (closingIdx > 0) {
|
|
157
|
+
raw = `${raw.slice(0, closingIdx)}accessCount: ${newCount}\n${raw.slice(closingIdx)}`;
|
|
158
|
+
}
|
|
108
159
|
}
|
|
109
160
|
writeFileSync(entry.filePath, raw);
|
|
110
161
|
entry.lastAccessed = now;
|
|
111
162
|
entry.accessCount = newCount;
|
|
112
163
|
}
|
|
113
|
-
catch {
|
|
164
|
+
catch {
|
|
165
|
+
/* ignore write errors */
|
|
166
|
+
}
|
|
114
167
|
}
|
|
115
168
|
/** Boost a memory's relevance score (capped at 1.0) */
|
|
116
169
|
export function boostRelevance(entry, amount = 0.1) {
|
|
117
170
|
const newRelevance = Math.min(1.0, (entry.relevance ?? 0.5) + amount);
|
|
118
171
|
try {
|
|
119
|
-
let raw = readFileSync(entry.filePath,
|
|
172
|
+
let raw = readFileSync(entry.filePath, "utf-8");
|
|
120
173
|
if (raw.match(/^relevance:/m)) {
|
|
121
174
|
raw = raw.replace(/^relevance:\s*[0-9.]+$/m, `relevance: ${newRelevance.toFixed(2)}`);
|
|
122
175
|
}
|
|
123
176
|
writeFileSync(entry.filePath, raw);
|
|
124
177
|
entry.relevance = newRelevance;
|
|
125
178
|
}
|
|
126
|
-
catch {
|
|
179
|
+
catch {
|
|
180
|
+
/* ignore */
|
|
181
|
+
}
|
|
127
182
|
}
|
|
128
183
|
/**
|
|
129
184
|
* Apply temporal decay to memory relevance.
|
|
@@ -164,30 +219,34 @@ export function loadActiveMemories() {
|
|
|
164
219
|
/** Delete memory files that have been pruned (relevance < 0.1) */
|
|
165
220
|
export function deletePrunedMemories(pruned) {
|
|
166
221
|
// Guard: only delete files within known memory directories
|
|
167
|
-
const allowedDirs = [PROJECT_MEMORY_DIR, GLOBAL_MEMORY_DIR].map(d => resolve(d));
|
|
222
|
+
const allowedDirs = [PROJECT_MEMORY_DIR, GLOBAL_MEMORY_DIR].map((d) => resolve(d));
|
|
168
223
|
let count = 0;
|
|
169
224
|
for (const m of pruned) {
|
|
170
225
|
const resolved = resolve(m.filePath);
|
|
171
|
-
if (!allowedDirs.some(d => resolved.startsWith(d + sep)))
|
|
226
|
+
if (!allowedDirs.some((d) => resolved.startsWith(d + sep)))
|
|
172
227
|
continue;
|
|
173
228
|
try {
|
|
174
229
|
unlinkSync(m.filePath);
|
|
175
230
|
count++;
|
|
176
231
|
}
|
|
177
|
-
catch {
|
|
232
|
+
catch {
|
|
233
|
+
/* ignore */
|
|
234
|
+
}
|
|
178
235
|
}
|
|
179
236
|
return count;
|
|
180
237
|
}
|
|
181
238
|
/** Write back decayed relevance score to file frontmatter */
|
|
182
239
|
function persistDecayedRelevance(entry) {
|
|
183
240
|
try {
|
|
184
|
-
let raw = readFileSync(entry.filePath,
|
|
241
|
+
let raw = readFileSync(entry.filePath, "utf-8");
|
|
185
242
|
if (raw.match(/^relevance:/m)) {
|
|
186
243
|
raw = raw.replace(/^relevance:\s*[0-9.]+$/m, `relevance: ${(entry.relevance ?? 0.5).toFixed(2)}`);
|
|
187
244
|
writeFileSync(entry.filePath, raw);
|
|
188
245
|
}
|
|
189
246
|
}
|
|
190
|
-
catch {
|
|
247
|
+
catch {
|
|
248
|
+
/* ignore */
|
|
249
|
+
}
|
|
191
250
|
}
|
|
192
251
|
/**
|
|
193
252
|
* Run full memory consolidation: apply decay, delete pruned files,
|
|
@@ -208,8 +267,54 @@ export function consolidateMemories() {
|
|
|
208
267
|
decayedCount++;
|
|
209
268
|
}
|
|
210
269
|
}
|
|
270
|
+
// Refresh MEMORY.md index after pruning
|
|
271
|
+
updateMemoryIndex(PROJECT_MEMORY_DIR);
|
|
272
|
+
updateMemoryIndex(GLOBAL_MEMORY_DIR);
|
|
211
273
|
return { total: all.length, pruned: prunedCount, decayed: decayedCount };
|
|
212
274
|
}
|
|
275
|
+
// ── User Profile ──
|
|
276
|
+
const USER_PROFILE_FILE = "USER.md";
|
|
277
|
+
const USER_PROFILE_MAX_CHARS = 2000;
|
|
278
|
+
/** Load the user profile from .oh/memory/USER.md */
|
|
279
|
+
export function loadUserProfile() {
|
|
280
|
+
const filePath = join(PROJECT_MEMORY_DIR, USER_PROFILE_FILE);
|
|
281
|
+
if (!existsSync(filePath))
|
|
282
|
+
return "";
|
|
283
|
+
try {
|
|
284
|
+
const raw = readFileSync(filePath, "utf-8");
|
|
285
|
+
const fmEnd = raw.indexOf("---", raw.indexOf("---") + 3);
|
|
286
|
+
return fmEnd > 0 ? raw.slice(fmEnd + 3).trim() : raw.trim();
|
|
287
|
+
}
|
|
288
|
+
catch {
|
|
289
|
+
return "";
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
/** Update the user profile, truncating to max chars */
|
|
293
|
+
export function updateUserProfile(content) {
|
|
294
|
+
mkdirSync(PROJECT_MEMORY_DIR, { recursive: true });
|
|
295
|
+
// Truncate at last newline before max chars to avoid cutting mid-sentence
|
|
296
|
+
let truncated = content;
|
|
297
|
+
if (truncated.length > USER_PROFILE_MAX_CHARS) {
|
|
298
|
+
const lastNewline = content.lastIndexOf("\n", USER_PROFILE_MAX_CHARS);
|
|
299
|
+
truncated = lastNewline > 0 ? content.slice(0, lastNewline) : content.slice(0, USER_PROFILE_MAX_CHARS);
|
|
300
|
+
}
|
|
301
|
+
const md = `---
|
|
302
|
+
name: User Profile
|
|
303
|
+
type: user_profile
|
|
304
|
+
updatedAt: ${Date.now()}
|
|
305
|
+
---
|
|
306
|
+
|
|
307
|
+
${truncated}
|
|
308
|
+
`;
|
|
309
|
+
writeFileSync(join(PROJECT_MEMORY_DIR, USER_PROFILE_FILE), md);
|
|
310
|
+
}
|
|
311
|
+
/** Format user profile for system prompt injection */
|
|
312
|
+
export function userProfileToPrompt() {
|
|
313
|
+
const profile = loadUserProfile();
|
|
314
|
+
if (!profile)
|
|
315
|
+
return "";
|
|
316
|
+
return `# User Profile\n${profile}`;
|
|
317
|
+
}
|
|
213
318
|
/**
|
|
214
319
|
* Detect if recent assistant messages contain learnable patterns.
|
|
215
320
|
* Returns structured memories to save, or empty array.
|
|
@@ -219,30 +324,26 @@ export async function detectMemories(provider, recentMessages, model) {
|
|
|
219
324
|
if (recentMessages.length < 4)
|
|
220
325
|
return [];
|
|
221
326
|
// Extract assistant messages from recent turns
|
|
222
|
-
const assistantMsgs = recentMessages
|
|
223
|
-
.filter(m => m.role === 'assistant' && m.content.length > 50)
|
|
224
|
-
.slice(-3);
|
|
327
|
+
const assistantMsgs = recentMessages.filter((m) => m.role === "assistant" && m.content.length > 50).slice(-3);
|
|
225
328
|
if (assistantMsgs.length === 0)
|
|
226
329
|
return [];
|
|
227
|
-
const contextText = assistantMsgs
|
|
228
|
-
.map(m => m.content.slice(0, 500))
|
|
229
|
-
.join('\n---\n');
|
|
330
|
+
const contextText = assistantMsgs.map((m) => m.content.slice(0, 500)).join("\n---\n");
|
|
230
331
|
const prompt = `Analyze this conversation snippet. If there are reusable learnings (coding conventions, project patterns, user preferences, debugging insights), extract them. Respond ONLY with a JSON array of objects with {name, type, description, content} or [] if nothing worth remembering.
|
|
231
332
|
|
|
232
|
-
Types: "
|
|
333
|
+
Types: "user" (role/preferences), "feedback" (corrections/confirmations), "project" (goals/decisions), "reference" (external pointers)
|
|
233
334
|
|
|
234
335
|
Keep each memory concise (1-2 sentences). Only extract non-obvious learnings.
|
|
235
336
|
|
|
236
337
|
${contextText}`;
|
|
237
338
|
try {
|
|
238
|
-
const response = await provider.complete([createUserMessage(prompt)],
|
|
339
|
+
const response = await provider.complete([createUserMessage(prompt)], "You are a memory extraction system. Respond ONLY with valid JSON.", undefined, model);
|
|
239
340
|
const jsonMatch = response.content.match(/\[[\s\S]*\]/);
|
|
240
341
|
if (!jsonMatch)
|
|
241
342
|
return [];
|
|
242
343
|
const parsed = JSON.parse(jsonMatch[0]);
|
|
243
344
|
if (!Array.isArray(parsed))
|
|
244
345
|
return [];
|
|
245
|
-
return parsed.filter((m) => m.name && m.type && m.content && typeof m.content ===
|
|
346
|
+
return parsed.filter((m) => m.name && m.type && m.content && typeof m.content === "string");
|
|
246
347
|
}
|
|
247
348
|
catch {
|
|
248
349
|
return [];
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Project auto-detection — detect language, framework, test runner, git state.
|
|
3
3
|
*/
|
|
4
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
5
|
-
import { join } from "node:path";
|
|
6
4
|
import { execSync } from "node:child_process";
|
|
5
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
7
6
|
import { platform, release } from "node:os";
|
|
7
|
+
import { join } from "node:path";
|
|
8
8
|
const DETECTORS = [
|
|
9
9
|
// [indicator, language, framework, packageManager, testRunner]
|
|
10
10
|
["pyproject.toml", "python", "", "pip", "pytest"],
|
|
@@ -30,7 +30,7 @@ const FRAMEWORKS = {
|
|
|
30
30
|
"svelte.config.js": "Svelte",
|
|
31
31
|
"manage.py": "Django",
|
|
32
32
|
"tailwind.config.js": "Tailwind CSS",
|
|
33
|
-
|
|
33
|
+
Dockerfile: "Docker",
|
|
34
34
|
"docker-compose.yml": "Docker Compose",
|
|
35
35
|
};
|
|
36
36
|
export function detectProject(root) {
|
|
@@ -63,7 +63,9 @@ export function detectProject(root) {
|
|
|
63
63
|
gitBranch = head.slice("ref: refs/heads/".length);
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
|
-
catch {
|
|
66
|
+
catch {
|
|
67
|
+
/* ignore */
|
|
68
|
+
}
|
|
67
69
|
}
|
|
68
70
|
const hasReadme = ["README.md", "README.rst", "README.txt", "README"].some((f) => existsSync(join(projectRoot, f)));
|
|
69
71
|
let description = "";
|
|
@@ -81,7 +83,17 @@ export function detectProject(root) {
|
|
|
81
83
|
break;
|
|
82
84
|
}
|
|
83
85
|
}
|
|
84
|
-
return {
|
|
86
|
+
return {
|
|
87
|
+
root: projectRoot,
|
|
88
|
+
language,
|
|
89
|
+
framework,
|
|
90
|
+
packageManager,
|
|
91
|
+
testRunner,
|
|
92
|
+
hasGit,
|
|
93
|
+
gitBranch,
|
|
94
|
+
hasReadme,
|
|
95
|
+
description,
|
|
96
|
+
};
|
|
85
97
|
}
|
|
86
98
|
export function projectContextToPrompt(ctx, model) {
|
|
87
99
|
const parts = [];
|
|
@@ -123,7 +135,9 @@ export function projectContextToPrompt(ctx, model) {
|
|
|
123
135
|
else if (refs.includes("master"))
|
|
124
136
|
mainBranch = "master";
|
|
125
137
|
}
|
|
126
|
-
catch {
|
|
138
|
+
catch {
|
|
139
|
+
/* ignore */
|
|
140
|
+
}
|
|
127
141
|
parts.push(`Main branch: ${mainBranch}`);
|
|
128
142
|
// Git user
|
|
129
143
|
try {
|
|
@@ -131,7 +145,9 @@ export function projectContextToPrompt(ctx, model) {
|
|
|
131
145
|
if (user)
|
|
132
146
|
parts.push(`Git user: ${user}`);
|
|
133
147
|
}
|
|
134
|
-
catch {
|
|
148
|
+
catch {
|
|
149
|
+
/* ignore */
|
|
150
|
+
}
|
|
135
151
|
// Git status (brief)
|
|
136
152
|
try {
|
|
137
153
|
const status = execSync("git status --porcelain", { cwd: ctx.root, stdio: "pipe" }).toString().trim();
|
|
@@ -140,15 +156,19 @@ export function projectContextToPrompt(ctx, model) {
|
|
|
140
156
|
parts.push(`\nStatus:\n${lines.join("\n")}${status.split("\n").length > 20 ? "\n..." : ""}`);
|
|
141
157
|
}
|
|
142
158
|
}
|
|
143
|
-
catch {
|
|
159
|
+
catch {
|
|
160
|
+
/* ignore */
|
|
161
|
+
}
|
|
144
162
|
// Recent commits
|
|
145
163
|
try {
|
|
146
164
|
const log = execSync("git log --oneline -5", { cwd: ctx.root, stdio: "pipe" }).toString().trim();
|
|
147
165
|
if (log)
|
|
148
166
|
parts.push(`\nRecent commits:\n${log}`);
|
|
149
167
|
}
|
|
150
|
-
catch {
|
|
168
|
+
catch {
|
|
169
|
+
/* ignore */
|
|
170
|
+
}
|
|
151
171
|
}
|
|
152
|
-
return
|
|
172
|
+
return `# Environment\n${parts.map((p) => `- ${p}`).join("\n")}`;
|
|
153
173
|
}
|
|
154
174
|
//# sourceMappingURL=onboarding.js.map
|
|
@@ -18,7 +18,7 @@ export type SkillMetadata = {
|
|
|
18
18
|
args: string[] | undefined;
|
|
19
19
|
content: string;
|
|
20
20
|
filePath: string;
|
|
21
|
-
source:
|
|
21
|
+
source: "project" | "global" | "plugin";
|
|
22
22
|
};
|
|
23
23
|
export type PluginManifest = {
|
|
24
24
|
name: string;
|
|
@@ -48,6 +48,14 @@ export declare function discoverSkills(): SkillMetadata[];
|
|
|
48
48
|
export declare function findSkill(name: string): SkillMetadata | null;
|
|
49
49
|
/** Find skills that match a trigger condition */
|
|
50
50
|
export declare function findTriggeredSkills(userMessage: string): SkillMetadata[];
|
|
51
|
+
/** Find a skill similar to a candidate (for patch-vs-create decision) */
|
|
52
|
+
export declare function findSimilarSkill(candidateName: string, candidateDescription: string, skills: Array<{
|
|
53
|
+
name: string;
|
|
54
|
+
description: string;
|
|
55
|
+
}>): {
|
|
56
|
+
name: string;
|
|
57
|
+
description: string;
|
|
58
|
+
} | null;
|
|
51
59
|
/** Load a plugin manifest from a directory */
|
|
52
60
|
export declare function loadPluginManifest(dir: string): PluginManifest | null;
|
|
53
61
|
/** Discover plugins from node_modules */
|
package/dist/harness/plugins.js
CHANGED
|
@@ -10,11 +10,11 @@
|
|
|
10
10
|
* 2. ~/.oh/skills/ (global)
|
|
11
11
|
* 3. node_modules packages with "openharness-plugin" keyword
|
|
12
12
|
*/
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
const PROJECT_SKILLS_DIR = join(
|
|
17
|
-
const GLOBAL_SKILLS_DIR = join(homedir(),
|
|
13
|
+
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
14
|
+
import { homedir } from "node:os";
|
|
15
|
+
import { basename, join } from "node:path";
|
|
16
|
+
const PROJECT_SKILLS_DIR = join(".oh", "skills");
|
|
17
|
+
const GLOBAL_SKILLS_DIR = join(homedir(), ".oh", "skills");
|
|
18
18
|
/** Parse YAML frontmatter from a skill markdown file */
|
|
19
19
|
function parseSkillFrontmatter(content) {
|
|
20
20
|
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
@@ -33,10 +33,10 @@ function parseSkillFrontmatter(content) {
|
|
|
33
33
|
result.trigger = triggerMatch[1].trim();
|
|
34
34
|
const toolsMatch = frontmatter.match(/^tools:\s*\[(.+)\]$/m);
|
|
35
35
|
if (toolsMatch)
|
|
36
|
-
result.tools = toolsMatch[1].split(
|
|
36
|
+
result.tools = toolsMatch[1].split(",").map((t) => t.trim());
|
|
37
37
|
const argsMatch = frontmatter.match(/^args:\s*\[(.+)\]$/m);
|
|
38
38
|
if (argsMatch)
|
|
39
|
-
result.args = argsMatch[1].split(
|
|
39
|
+
result.args = argsMatch[1].split(",").map((a) => a.trim());
|
|
40
40
|
return result;
|
|
41
41
|
}
|
|
42
42
|
/** Load skills from a directory */
|
|
@@ -44,15 +44,15 @@ function loadSkillsFromDir(dir, source) {
|
|
|
44
44
|
if (!existsSync(dir))
|
|
45
45
|
return [];
|
|
46
46
|
return readdirSync(dir)
|
|
47
|
-
.filter(f => f.endsWith(
|
|
48
|
-
.map(f => {
|
|
47
|
+
.filter((f) => f.endsWith(".md"))
|
|
48
|
+
.map((f) => {
|
|
49
49
|
const filePath = join(dir, f);
|
|
50
50
|
try {
|
|
51
|
-
const content = readFileSync(filePath,
|
|
51
|
+
const content = readFileSync(filePath, "utf-8");
|
|
52
52
|
const meta = parseSkillFrontmatter(content);
|
|
53
53
|
return {
|
|
54
|
-
name: meta.name || basename(f,
|
|
55
|
-
description: meta.description ||
|
|
54
|
+
name: meta.name || basename(f, ".md"),
|
|
55
|
+
description: meta.description || "",
|
|
56
56
|
trigger: meta.trigger,
|
|
57
57
|
tools: meta.tools,
|
|
58
58
|
args: meta.args,
|
|
@@ -70,14 +70,14 @@ function loadSkillsFromDir(dir, source) {
|
|
|
70
70
|
/** Discover all available skills from project + global dirs + installed plugins */
|
|
71
71
|
export function discoverSkills() {
|
|
72
72
|
const skills = [];
|
|
73
|
-
skills.push(...loadSkillsFromDir(PROJECT_SKILLS_DIR,
|
|
74
|
-
skills.push(...loadSkillsFromDir(GLOBAL_SKILLS_DIR,
|
|
73
|
+
skills.push(...loadSkillsFromDir(PROJECT_SKILLS_DIR, "project"));
|
|
74
|
+
skills.push(...loadSkillsFromDir(GLOBAL_SKILLS_DIR, "global"));
|
|
75
75
|
// Load skills from installed marketplace plugins (namespaced as plugin-name:skill-name)
|
|
76
76
|
try {
|
|
77
|
-
const { getInstalledPlugins } = require(
|
|
77
|
+
const { getInstalledPlugins } = require("./marketplace.js");
|
|
78
78
|
for (const plugin of getInstalledPlugins()) {
|
|
79
|
-
const pluginSkillsDir = join(plugin.cachePath,
|
|
80
|
-
const pluginSkills = loadSkillsFromDir(pluginSkillsDir,
|
|
79
|
+
const pluginSkillsDir = join(plugin.cachePath, "skills");
|
|
80
|
+
const pluginSkills = loadSkillsFromDir(pluginSkillsDir, "plugin");
|
|
81
81
|
// Namespace: prefix skill name with plugin name
|
|
82
82
|
for (const skill of pluginSkills) {
|
|
83
83
|
skill.name = `${plugin.name}:${skill.name}`;
|
|
@@ -85,30 +85,50 @@ export function discoverSkills() {
|
|
|
85
85
|
skills.push(...pluginSkills);
|
|
86
86
|
}
|
|
87
87
|
}
|
|
88
|
-
catch {
|
|
88
|
+
catch {
|
|
89
|
+
/* marketplace module may not be loaded yet */
|
|
90
|
+
}
|
|
89
91
|
return skills;
|
|
90
92
|
}
|
|
91
93
|
/** Find a skill by name (case-insensitive) */
|
|
92
94
|
export function findSkill(name) {
|
|
93
95
|
const skills = discoverSkills();
|
|
94
|
-
return skills.find(s => s.name.toLowerCase() === name.toLowerCase()) ?? null;
|
|
96
|
+
return skills.find((s) => s.name.toLowerCase() === name.toLowerCase()) ?? null;
|
|
95
97
|
}
|
|
96
98
|
/** Find skills that match a trigger condition */
|
|
97
99
|
export function findTriggeredSkills(userMessage) {
|
|
98
100
|
const skills = discoverSkills();
|
|
99
|
-
return skills.filter(s => {
|
|
101
|
+
return skills.filter((s) => {
|
|
100
102
|
if (!s.trigger)
|
|
101
103
|
return false;
|
|
102
104
|
return userMessage.toLowerCase().includes(s.trigger.toLowerCase());
|
|
103
105
|
});
|
|
104
106
|
}
|
|
107
|
+
/** Find a skill similar to a candidate (for patch-vs-create decision) */
|
|
108
|
+
export function findSimilarSkill(candidateName, candidateDescription, skills) {
|
|
109
|
+
const nameWords = new Set(candidateName.toLowerCase().split(/[-_ ]+/));
|
|
110
|
+
for (const skill of skills) {
|
|
111
|
+
const skillWords = new Set(skill.name.toLowerCase().split(/[-_ ]+/));
|
|
112
|
+
const overlap = [...nameWords].filter((w) => skillWords.has(w)).length;
|
|
113
|
+
if (overlap >= Math.ceil(nameWords.size * 0.5))
|
|
114
|
+
return skill;
|
|
115
|
+
const descWords = new Set(skill.description.toLowerCase().split(/\s+/));
|
|
116
|
+
const descOverlap = candidateDescription
|
|
117
|
+
.toLowerCase()
|
|
118
|
+
.split(/\s+/)
|
|
119
|
+
.filter((w) => descWords.has(w)).length;
|
|
120
|
+
if (descOverlap >= 3)
|
|
121
|
+
return skill;
|
|
122
|
+
}
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
105
125
|
/** Load a plugin manifest from a directory */
|
|
106
126
|
export function loadPluginManifest(dir) {
|
|
107
|
-
const manifestPath = join(dir,
|
|
127
|
+
const manifestPath = join(dir, "openharness-plugin.json");
|
|
108
128
|
if (!existsSync(manifestPath))
|
|
109
129
|
return null;
|
|
110
130
|
try {
|
|
111
|
-
return JSON.parse(readFileSync(manifestPath,
|
|
131
|
+
return JSON.parse(readFileSync(manifestPath, "utf-8"));
|
|
112
132
|
}
|
|
113
133
|
catch {
|
|
114
134
|
return null;
|
|
@@ -118,19 +138,19 @@ export function loadPluginManifest(dir) {
|
|
|
118
138
|
export function discoverPlugins() {
|
|
119
139
|
const plugins = [];
|
|
120
140
|
// Check node_modules for packages with openharness-plugin.json
|
|
121
|
-
const nodeModules = join(
|
|
141
|
+
const nodeModules = join(".", "node_modules");
|
|
122
142
|
if (!existsSync(nodeModules))
|
|
123
143
|
return plugins;
|
|
124
144
|
try {
|
|
125
145
|
for (const pkg of readdirSync(nodeModules)) {
|
|
126
|
-
if (pkg.startsWith(
|
|
146
|
+
if (pkg.startsWith("."))
|
|
127
147
|
continue;
|
|
128
148
|
const pkgDir = join(nodeModules, pkg);
|
|
129
149
|
const manifest = loadPluginManifest(pkgDir);
|
|
130
150
|
if (manifest)
|
|
131
151
|
plugins.push(manifest);
|
|
132
152
|
// Scoped packages
|
|
133
|
-
if (pkg.startsWith(
|
|
153
|
+
if (pkg.startsWith("@")) {
|
|
134
154
|
try {
|
|
135
155
|
for (const sub of readdirSync(pkgDir)) {
|
|
136
156
|
const subDir = join(pkgDir, sub);
|
|
@@ -139,18 +159,22 @@ export function discoverPlugins() {
|
|
|
139
159
|
plugins.push(subManifest);
|
|
140
160
|
}
|
|
141
161
|
}
|
|
142
|
-
catch {
|
|
162
|
+
catch {
|
|
163
|
+
/* ignore */
|
|
164
|
+
}
|
|
143
165
|
}
|
|
144
166
|
}
|
|
145
167
|
}
|
|
146
|
-
catch {
|
|
168
|
+
catch {
|
|
169
|
+
/* ignore */
|
|
170
|
+
}
|
|
147
171
|
return plugins;
|
|
148
172
|
}
|
|
149
173
|
/** Build a prompt listing available skills for the LLM */
|
|
150
174
|
export function skillsToPrompt(skills) {
|
|
151
175
|
if (skills.length === 0)
|
|
152
|
-
return
|
|
153
|
-
const lines = skills.map(s => `- ${s.name}: ${s.description}${s.trigger ? ` (auto-trigger: "${s.trigger}")` :
|
|
154
|
-
return `# Available Skills\nUse the Skill tool to invoke these:\n${lines.join(
|
|
176
|
+
return "";
|
|
177
|
+
const lines = skills.map((s) => `- ${s.name}: ${s.description}${s.trigger ? ` (auto-trigger: "${s.trigger}")` : ""}`);
|
|
178
|
+
return `# Available Skills\nUse the Skill tool to invoke these:\n${lines.join("\n")}`;
|
|
155
179
|
}
|
|
156
180
|
//# sourceMappingURL=plugins.js.map
|