@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
|
@@ -6,23 +6,23 @@
|
|
|
6
6
|
* Not bulletproof (key derivation is deterministic from machine info),
|
|
7
7
|
* but far better than plaintext YAML.
|
|
8
8
|
*/
|
|
9
|
-
import { createCipheriv, createDecipheriv, randomBytes, scryptSync } from
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
const CRED_DIR = join(homedir(),
|
|
14
|
-
const CRED_PATH = join(CRED_DIR,
|
|
15
|
-
const ALGORITHM =
|
|
9
|
+
import { createCipheriv, createDecipheriv, randomBytes, scryptSync } from "node:crypto";
|
|
10
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
11
|
+
import { homedir, hostname, userInfo } from "node:os";
|
|
12
|
+
import { join } from "node:path";
|
|
13
|
+
const CRED_DIR = join(homedir(), ".oh");
|
|
14
|
+
const CRED_PATH = join(CRED_DIR, "credentials.enc");
|
|
15
|
+
const ALGORITHM = "aes-256-gcm";
|
|
16
16
|
/** Derive an encryption key from machine identity */
|
|
17
17
|
function deriveKey() {
|
|
18
18
|
const identity = `${hostname()}-${userInfo().username}-openharness`;
|
|
19
|
-
return scryptSync(identity,
|
|
19
|
+
return scryptSync(identity, "oh-credential-salt", 32);
|
|
20
20
|
}
|
|
21
21
|
function encrypt(data) {
|
|
22
22
|
const key = deriveKey();
|
|
23
23
|
const iv = randomBytes(12);
|
|
24
24
|
const cipher = createCipheriv(ALGORITHM, key, iv);
|
|
25
|
-
const encrypted = Buffer.concat([cipher.update(data,
|
|
25
|
+
const encrypted = Buffer.concat([cipher.update(data, "utf-8"), cipher.final()]);
|
|
26
26
|
const tag = cipher.getAuthTag();
|
|
27
27
|
// Format: [iv (12)] [tag (16)] [encrypted data]
|
|
28
28
|
return Buffer.concat([iv, tag, encrypted]);
|
|
@@ -34,7 +34,7 @@ function decrypt(data) {
|
|
|
34
34
|
const encrypted = data.subarray(28);
|
|
35
35
|
const decipher = createDecipheriv(ALGORITHM, key, iv);
|
|
36
36
|
decipher.setAuthTag(tag);
|
|
37
|
-
return decipher.update(encrypted, undefined,
|
|
37
|
+
return decipher.update(encrypted, undefined, "utf-8") + decipher.final("utf-8");
|
|
38
38
|
}
|
|
39
39
|
function loadStore() {
|
|
40
40
|
if (!existsSync(CRED_PATH))
|
|
@@ -82,9 +82,9 @@ export function listCredentials() {
|
|
|
82
82
|
export function resolveApiKey(provider, configApiKey) {
|
|
83
83
|
// Environment variable names by provider
|
|
84
84
|
const envVarMap = {
|
|
85
|
-
anthropic:
|
|
86
|
-
openai:
|
|
87
|
-
openrouter:
|
|
85
|
+
anthropic: "ANTHROPIC_API_KEY",
|
|
86
|
+
openai: "OPENAI_API_KEY",
|
|
87
|
+
openrouter: "OPENROUTER_API_KEY",
|
|
88
88
|
};
|
|
89
89
|
const envVar = envVarMap[provider];
|
|
90
90
|
if (envVar && process.env[envVar])
|
package/dist/harness/hooks.js
CHANGED
|
@@ -103,14 +103,14 @@ async function runHttpHook(url, event, ctx, timeoutMs = 10_000) {
|
|
|
103
103
|
try {
|
|
104
104
|
const body = JSON.stringify({ event, ...ctx });
|
|
105
105
|
const res = await fetch(url, {
|
|
106
|
-
method:
|
|
107
|
-
headers: {
|
|
106
|
+
method: "POST",
|
|
107
|
+
headers: { "Content-Type": "application/json" },
|
|
108
108
|
body,
|
|
109
109
|
signal: AbortSignal.timeout(timeoutMs),
|
|
110
110
|
});
|
|
111
111
|
if (!res.ok)
|
|
112
112
|
return false;
|
|
113
|
-
const data = await res.json();
|
|
113
|
+
const data = (await res.json());
|
|
114
114
|
return data.allowed !== false;
|
|
115
115
|
}
|
|
116
116
|
catch {
|
|
@@ -118,7 +118,7 @@ async function runHttpHook(url, event, ctx, timeoutMs = 10_000) {
|
|
|
118
118
|
}
|
|
119
119
|
}
|
|
120
120
|
/** Run a prompt hook. Uses LLM to make a yes/no decision. */
|
|
121
|
-
async function runPromptHook(
|
|
121
|
+
async function runPromptHook(_promptText, _ctx) {
|
|
122
122
|
// Prompt hooks require a provider — skip if not available
|
|
123
123
|
// This is a lightweight check; full LLM call would need provider injection
|
|
124
124
|
// For now, prompt hooks evaluate the prompt text as a simple template
|
|
@@ -177,7 +177,9 @@ export function emitHook(event, ctx = {}) {
|
|
|
177
177
|
for (const def of defs) {
|
|
178
178
|
if (!matchesHook(def, ctx))
|
|
179
179
|
continue;
|
|
180
|
-
executeHookDef(def, event, ctx).catch(() => {
|
|
180
|
+
executeHookDef(def, event, ctx).catch(() => {
|
|
181
|
+
/* fire-and-forget: non-preToolUse hooks must not block the agent loop */
|
|
182
|
+
});
|
|
181
183
|
}
|
|
182
184
|
return true;
|
|
183
185
|
}
|
|
@@ -10,10 +10,10 @@
|
|
|
10
10
|
*
|
|
11
11
|
* Supports single keys and two-key chord sequences (e.g., "ctrl+k ctrl+d").
|
|
12
12
|
*/
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
const KEYBINDINGS_PATH = join(homedir(),
|
|
13
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
14
|
+
import { homedir } from "node:os";
|
|
15
|
+
import { join } from "node:path";
|
|
16
|
+
const KEYBINDINGS_PATH = join(homedir(), ".oh", "keybindings.json");
|
|
17
17
|
let cachedBindings = null;
|
|
18
18
|
/** Load keybindings from ~/.oh/keybindings.json */
|
|
19
19
|
export function loadKeybindings() {
|
|
@@ -24,37 +24,39 @@ export function loadKeybindings() {
|
|
|
24
24
|
return cachedBindings;
|
|
25
25
|
}
|
|
26
26
|
try {
|
|
27
|
-
const raw = readFileSync(KEYBINDINGS_PATH,
|
|
27
|
+
const raw = readFileSync(KEYBINDINGS_PATH, "utf-8");
|
|
28
28
|
const parsed = JSON.parse(raw);
|
|
29
29
|
if (Array.isArray(parsed)) {
|
|
30
30
|
cachedBindings = parsed;
|
|
31
31
|
return cachedBindings;
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
|
-
catch {
|
|
34
|
+
catch {
|
|
35
|
+
/* ignore parse errors */
|
|
36
|
+
}
|
|
35
37
|
cachedBindings = defaultKeybindings();
|
|
36
38
|
return cachedBindings;
|
|
37
39
|
}
|
|
38
40
|
/** Default keybindings */
|
|
39
41
|
function defaultKeybindings() {
|
|
40
42
|
return [
|
|
41
|
-
{ key:
|
|
42
|
-
{ key:
|
|
43
|
-
{ key:
|
|
44
|
-
{ key:
|
|
45
|
-
{ key:
|
|
46
|
-
{ key:
|
|
47
|
-
{ key:
|
|
43
|
+
{ key: "ctrl+d", action: "/diff" },
|
|
44
|
+
{ key: "ctrl+l", action: "/clear" },
|
|
45
|
+
{ key: "ctrl+u", action: "/undo" },
|
|
46
|
+
{ key: "ctrl+s", action: "/status" },
|
|
47
|
+
{ key: "ctrl+k ctrl+c", action: "/cost" },
|
|
48
|
+
{ key: "ctrl+k ctrl+f", action: "/fast" },
|
|
49
|
+
{ key: "ctrl+k ctrl+l", action: "/log" },
|
|
48
50
|
];
|
|
49
51
|
}
|
|
50
52
|
/** Parse a key string like "ctrl+s" into components */
|
|
51
53
|
function parseKeyString(keyStr) {
|
|
52
|
-
const parts = keyStr.toLowerCase().split(
|
|
54
|
+
const parts = keyStr.toLowerCase().split("+");
|
|
53
55
|
return {
|
|
54
|
-
ctrl: parts.includes(
|
|
55
|
-
alt: parts.includes(
|
|
56
|
-
shift: parts.includes(
|
|
57
|
-
key: parts.filter(p => p !==
|
|
56
|
+
ctrl: parts.includes("ctrl"),
|
|
57
|
+
alt: parts.includes("alt"),
|
|
58
|
+
shift: parts.includes("shift"),
|
|
59
|
+
key: parts.filter((p) => p !== "ctrl" && p !== "alt" && p !== "shift")[0] ?? "",
|
|
58
60
|
};
|
|
59
61
|
}
|
|
60
62
|
/** Check if an Ink key event matches a parsed key */
|
|
@@ -15,13 +15,13 @@ export type MarketplaceEntry = {
|
|
|
15
15
|
keywords?: string[];
|
|
16
16
|
};
|
|
17
17
|
export type MarketplaceSource = {
|
|
18
|
-
type:
|
|
18
|
+
type: "github";
|
|
19
19
|
repo: string;
|
|
20
20
|
} | {
|
|
21
|
-
type:
|
|
21
|
+
type: "npm";
|
|
22
22
|
package: string;
|
|
23
23
|
} | {
|
|
24
|
-
type:
|
|
24
|
+
type: "url";
|
|
25
25
|
url: string;
|
|
26
26
|
};
|
|
27
27
|
export type Marketplace = {
|
|
@@ -6,13 +6,13 @@
|
|
|
6
6
|
*
|
|
7
7
|
* Inspired by Claude Code's marketplace model.
|
|
8
8
|
*/
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import { homedir } from
|
|
12
|
-
import {
|
|
13
|
-
const MARKETPLACE_DIR = join(homedir(),
|
|
14
|
-
const PLUGIN_CACHE_DIR = join(homedir(),
|
|
15
|
-
const INSTALLED_PLUGINS_FILE = join(homedir(),
|
|
9
|
+
import { execSync } from "node:child_process";
|
|
10
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
11
|
+
import { homedir } from "node:os";
|
|
12
|
+
import { basename, join } from "node:path";
|
|
13
|
+
const MARKETPLACE_DIR = join(homedir(), ".oh", "marketplaces");
|
|
14
|
+
const PLUGIN_CACHE_DIR = join(homedir(), ".oh", "plugins", "cache");
|
|
15
|
+
const INSTALLED_PLUGINS_FILE = join(homedir(), ".oh", "plugins", "installed.json");
|
|
16
16
|
// ── Marketplace Management ──
|
|
17
17
|
/** Add a marketplace from a URL, GitHub repo, or local path */
|
|
18
18
|
export function addMarketplace(nameOrUrl) {
|
|
@@ -20,30 +20,30 @@ export function addMarketplace(nameOrUrl) {
|
|
|
20
20
|
// Fetch marketplace.json
|
|
21
21
|
let data;
|
|
22
22
|
let marketplaceName;
|
|
23
|
-
if (nameOrUrl.startsWith(
|
|
23
|
+
if (nameOrUrl.startsWith("http")) {
|
|
24
24
|
// URL
|
|
25
25
|
try {
|
|
26
|
-
data = execSync(`curl -sL "${nameOrUrl}/marketplace.json"`, { encoding:
|
|
26
|
+
data = execSync(`curl -sL "${nameOrUrl}/marketplace.json"`, { encoding: "utf-8", timeout: 10_000 });
|
|
27
27
|
marketplaceName = new URL(nameOrUrl).hostname;
|
|
28
28
|
}
|
|
29
29
|
catch {
|
|
30
30
|
return null;
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
|
-
else if (nameOrUrl.includes(
|
|
33
|
+
else if (nameOrUrl.includes("/") && !nameOrUrl.startsWith(".")) {
|
|
34
34
|
// GitHub repo (owner/repo format)
|
|
35
35
|
try {
|
|
36
36
|
const url = `https://raw.githubusercontent.com/${nameOrUrl}/main/marketplace.json`;
|
|
37
|
-
data = execSync(`curl -sL "${url}"`, { encoding:
|
|
38
|
-
marketplaceName = nameOrUrl.replace(
|
|
37
|
+
data = execSync(`curl -sL "${url}"`, { encoding: "utf-8", timeout: 10_000 });
|
|
38
|
+
marketplaceName = nameOrUrl.replace("/", "-");
|
|
39
39
|
}
|
|
40
40
|
catch {
|
|
41
41
|
return null;
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
|
-
else if (existsSync(join(nameOrUrl,
|
|
44
|
+
else if (existsSync(join(nameOrUrl, "marketplace.json"))) {
|
|
45
45
|
// Local path
|
|
46
|
-
data = readFileSync(join(nameOrUrl,
|
|
46
|
+
data = readFileSync(join(nameOrUrl, "marketplace.json"), "utf-8");
|
|
47
47
|
marketplaceName = basename(nameOrUrl);
|
|
48
48
|
}
|
|
49
49
|
else {
|
|
@@ -79,10 +79,10 @@ export function listMarketplaces() {
|
|
|
79
79
|
if (!existsSync(MARKETPLACE_DIR))
|
|
80
80
|
return [];
|
|
81
81
|
return readdirSync(MARKETPLACE_DIR)
|
|
82
|
-
.filter(f => f.endsWith(
|
|
83
|
-
.map(f => {
|
|
82
|
+
.filter((f) => f.endsWith(".json"))
|
|
83
|
+
.map((f) => {
|
|
84
84
|
try {
|
|
85
|
-
return JSON.parse(readFileSync(join(MARKETPLACE_DIR, f),
|
|
85
|
+
return JSON.parse(readFileSync(join(MARKETPLACE_DIR, f), "utf-8"));
|
|
86
86
|
}
|
|
87
87
|
catch {
|
|
88
88
|
return null;
|
|
@@ -98,7 +98,7 @@ export function searchMarketplace(query) {
|
|
|
98
98
|
for (const plugin of mp.plugins) {
|
|
99
99
|
if (plugin.name.toLowerCase().includes(q) ||
|
|
100
100
|
plugin.description.toLowerCase().includes(q) ||
|
|
101
|
-
plugin.keywords?.some(k => k.toLowerCase().includes(q))) {
|
|
101
|
+
plugin.keywords?.some((k) => k.toLowerCase().includes(q))) {
|
|
102
102
|
results.push({ ...plugin, marketplace: mp.name });
|
|
103
103
|
}
|
|
104
104
|
}
|
|
@@ -111,11 +111,11 @@ export function installPlugin(pluginName, marketplaceName) {
|
|
|
111
111
|
// Find the plugin in marketplaces
|
|
112
112
|
const marketplaces = listMarketplaces();
|
|
113
113
|
let entry = null;
|
|
114
|
-
let fromMarketplace =
|
|
114
|
+
let fromMarketplace = "";
|
|
115
115
|
for (const mp of marketplaces) {
|
|
116
116
|
if (marketplaceName && mp.name !== marketplaceName)
|
|
117
117
|
continue;
|
|
118
|
-
const found = mp.plugins.find(p => p.name === pluginName);
|
|
118
|
+
const found = mp.plugins.find((p) => p.name === pluginName);
|
|
119
119
|
if (found) {
|
|
120
120
|
entry = found;
|
|
121
121
|
fromMarketplace = mp.name;
|
|
@@ -129,24 +129,33 @@ export function installPlugin(pluginName, marketplaceName) {
|
|
|
129
129
|
mkdirSync(cacheDir, { recursive: true });
|
|
130
130
|
try {
|
|
131
131
|
switch (entry.source.type) {
|
|
132
|
-
case
|
|
132
|
+
case "github": {
|
|
133
133
|
// Clone the repo to cache
|
|
134
|
-
execSync(`git clone --depth 1 "https://github.com/${entry.source.repo}.git" "${cacheDir}"`, {
|
|
134
|
+
execSync(`git clone --depth 1 "https://github.com/${entry.source.repo}.git" "${cacheDir}"`, {
|
|
135
|
+
stdio: "pipe",
|
|
136
|
+
timeout: 30_000,
|
|
137
|
+
});
|
|
135
138
|
break;
|
|
136
139
|
}
|
|
137
|
-
case
|
|
140
|
+
case "npm": {
|
|
138
141
|
// Install npm package to cache
|
|
139
|
-
execSync(`npm pack "${entry.source.package}" --pack-destination "${cacheDir}"`, {
|
|
142
|
+
execSync(`npm pack "${entry.source.package}" --pack-destination "${cacheDir}"`, {
|
|
143
|
+
stdio: "pipe",
|
|
144
|
+
timeout: 30_000,
|
|
145
|
+
});
|
|
140
146
|
// Extract the tarball
|
|
141
|
-
const tgz = readdirSync(cacheDir).find(f => f.endsWith(
|
|
147
|
+
const tgz = readdirSync(cacheDir).find((f) => f.endsWith(".tgz"));
|
|
142
148
|
if (tgz) {
|
|
143
|
-
execSync(`tar xzf "${join(cacheDir, tgz)}" -C "${cacheDir}" --strip-components=1`, { stdio:
|
|
149
|
+
execSync(`tar xzf "${join(cacheDir, tgz)}" -C "${cacheDir}" --strip-components=1`, { stdio: "pipe" });
|
|
144
150
|
}
|
|
145
151
|
break;
|
|
146
152
|
}
|
|
147
|
-
case
|
|
148
|
-
execSync(`curl -sL "${entry.source.url}" -o "${join(cacheDir,
|
|
149
|
-
|
|
153
|
+
case "url": {
|
|
154
|
+
execSync(`curl -sL "${entry.source.url}" -o "${join(cacheDir, "plugin.tar.gz")}"`, {
|
|
155
|
+
stdio: "pipe",
|
|
156
|
+
timeout: 30_000,
|
|
157
|
+
});
|
|
158
|
+
execSync(`tar xzf "${join(cacheDir, "plugin.tar.gz")}" -C "${cacheDir}"`, { stdio: "pipe" });
|
|
150
159
|
break;
|
|
151
160
|
}
|
|
152
161
|
}
|
|
@@ -156,7 +165,9 @@ export function installPlugin(pluginName, marketplaceName) {
|
|
|
156
165
|
try {
|
|
157
166
|
rmSync(cacheDir, { recursive: true });
|
|
158
167
|
}
|
|
159
|
-
catch {
|
|
168
|
+
catch {
|
|
169
|
+
/* ignore */
|
|
170
|
+
}
|
|
160
171
|
return null;
|
|
161
172
|
}
|
|
162
173
|
// Record installation
|
|
@@ -173,16 +184,18 @@ export function installPlugin(pluginName, marketplaceName) {
|
|
|
173
184
|
/** Uninstall a plugin */
|
|
174
185
|
export function uninstallPlugin(name) {
|
|
175
186
|
const installed = getInstalledPlugins();
|
|
176
|
-
const plugin = installed.find(p => p.name === name);
|
|
187
|
+
const plugin = installed.find((p) => p.name === name);
|
|
177
188
|
if (!plugin)
|
|
178
189
|
return false;
|
|
179
190
|
// Remove from cache
|
|
180
191
|
try {
|
|
181
192
|
rmSync(plugin.cachePath, { recursive: true });
|
|
182
193
|
}
|
|
183
|
-
catch {
|
|
194
|
+
catch {
|
|
195
|
+
/* ignore */
|
|
196
|
+
}
|
|
184
197
|
// Remove from installed list
|
|
185
|
-
const remaining = installed.filter(p => p.name !== name);
|
|
198
|
+
const remaining = installed.filter((p) => p.name !== name);
|
|
186
199
|
saveInstalledPluginList(remaining);
|
|
187
200
|
return true;
|
|
188
201
|
}
|
|
@@ -191,7 +204,7 @@ export function getInstalledPlugins() {
|
|
|
191
204
|
if (!existsSync(INSTALLED_PLUGINS_FILE))
|
|
192
205
|
return [];
|
|
193
206
|
try {
|
|
194
|
-
return JSON.parse(readFileSync(INSTALLED_PLUGINS_FILE,
|
|
207
|
+
return JSON.parse(readFileSync(INSTALLED_PLUGINS_FILE, "utf-8"));
|
|
195
208
|
}
|
|
196
209
|
catch {
|
|
197
210
|
return [];
|
|
@@ -200,7 +213,7 @@ export function getInstalledPlugins() {
|
|
|
200
213
|
function saveInstalledPlugin(plugin) {
|
|
201
214
|
const installed = getInstalledPlugins();
|
|
202
215
|
// Replace existing version
|
|
203
|
-
const idx = installed.findIndex(p => p.name === plugin.name);
|
|
216
|
+
const idx = installed.findIndex((p) => p.name === plugin.name);
|
|
204
217
|
if (idx >= 0)
|
|
205
218
|
installed[idx] = plugin;
|
|
206
219
|
else
|
|
@@ -208,7 +221,7 @@ function saveInstalledPlugin(plugin) {
|
|
|
208
221
|
saveInstalledPluginList(installed);
|
|
209
222
|
}
|
|
210
223
|
function saveInstalledPluginList(plugins) {
|
|
211
|
-
const dir = join(homedir(),
|
|
224
|
+
const dir = join(homedir(), ".oh", "plugins");
|
|
212
225
|
mkdirSync(dir, { recursive: true });
|
|
213
226
|
writeFileSync(INSTALLED_PLUGINS_FILE, JSON.stringify(plugins, null, 2));
|
|
214
227
|
}
|
|
@@ -216,27 +229,27 @@ function saveInstalledPluginList(plugins) {
|
|
|
216
229
|
/** Format marketplace entries for display */
|
|
217
230
|
export function formatMarketplaceSearch(results) {
|
|
218
231
|
if (results.length === 0)
|
|
219
|
-
return
|
|
232
|
+
return "No plugins found.";
|
|
220
233
|
const lines = [`Found ${results.length} plugin(s):\n`];
|
|
221
234
|
for (const r of results) {
|
|
222
235
|
lines.push(` ${r.name}@${r.version} [${r.marketplace}]`);
|
|
223
236
|
lines.push(` ${r.description}`);
|
|
224
237
|
if (r.author)
|
|
225
238
|
lines.push(` by ${r.author}`);
|
|
226
|
-
lines.push(
|
|
239
|
+
lines.push("");
|
|
227
240
|
}
|
|
228
|
-
lines.push(
|
|
229
|
-
return lines.join(
|
|
241
|
+
lines.push("Install with: /plugin install <name>");
|
|
242
|
+
return lines.join("\n");
|
|
230
243
|
}
|
|
231
244
|
/** Format installed plugins for display */
|
|
232
245
|
export function formatInstalledPlugins(plugins) {
|
|
233
246
|
if (plugins.length === 0)
|
|
234
|
-
return
|
|
247
|
+
return "No plugins installed from marketplaces.";
|
|
235
248
|
const lines = [`Installed Plugins (${plugins.length}):\n`];
|
|
236
249
|
for (const p of plugins) {
|
|
237
250
|
const age = Math.round((Date.now() - p.installedAt) / (1000 * 60 * 60 * 24));
|
|
238
251
|
lines.push(` ${p.name}@${p.version} [${p.marketplace}] ${age}d ago`);
|
|
239
252
|
}
|
|
240
|
-
return lines.join(
|
|
253
|
+
return lines.join("\n");
|
|
241
254
|
}
|
|
242
255
|
//# sourceMappingURL=marketplace.js.map
|
package/dist/harness/memory.d.ts
CHANGED
|
@@ -7,11 +7,17 @@
|
|
|
7
7
|
* The system detects learnable patterns from assistant responses and saves them
|
|
8
8
|
* without user intervention.
|
|
9
9
|
*/
|
|
10
|
-
import type {
|
|
11
|
-
import type {
|
|
10
|
+
import type { Provider } from "../providers/base.js";
|
|
11
|
+
import type { Message } from "../types/message.js";
|
|
12
|
+
/**
|
|
13
|
+
* Memory types — supports both legacy and Claude Code-compatible names.
|
|
14
|
+
* Legacy: convention, preference, project, debugging
|
|
15
|
+
* New: user, feedback, project, reference
|
|
16
|
+
*/
|
|
17
|
+
export type MemoryType = "convention" | "preference" | "project" | "debugging" | "user" | "feedback" | "reference";
|
|
12
18
|
export type MemoryEntry = {
|
|
13
19
|
name: string;
|
|
14
|
-
type:
|
|
20
|
+
type: MemoryType;
|
|
15
21
|
description: string;
|
|
16
22
|
content: string;
|
|
17
23
|
filePath: string;
|
|
@@ -25,7 +31,13 @@ export declare function loadMemories(): MemoryEntry[];
|
|
|
25
31
|
/** Build a system prompt section from loaded memories */
|
|
26
32
|
export declare function memoriesToPrompt(memories: MemoryEntry[]): string;
|
|
27
33
|
/** Save a memory entry to the project memory directory */
|
|
28
|
-
export declare function saveMemory(name: string, type:
|
|
34
|
+
export declare function saveMemory(name: string, type: MemoryType, description: string, content: string, global?: boolean): string;
|
|
35
|
+
/**
|
|
36
|
+
* Update or create MEMORY.md index file in the given memory directory.
|
|
37
|
+
* The index is always loaded into context, providing instant awareness of all stored memories.
|
|
38
|
+
* Each entry is a one-liner pointer to the individual memory file (~200 line cap).
|
|
39
|
+
*/
|
|
40
|
+
export declare function updateMemoryIndex(dir?: string): void;
|
|
29
41
|
/** Mark a memory as accessed — updates lastAccessed and accessCount in the file */
|
|
30
42
|
export declare function touchMemory(entry: MemoryEntry): void;
|
|
31
43
|
/** Boost a memory's relevance score (capped at 1.0) */
|
|
@@ -53,13 +65,19 @@ export type ConsolidationResult = {
|
|
|
53
65
|
* persist updated relevance scores. Designed to run on session end.
|
|
54
66
|
*/
|
|
55
67
|
export declare function consolidateMemories(): ConsolidationResult;
|
|
68
|
+
/** Load the user profile from .oh/memory/USER.md */
|
|
69
|
+
export declare function loadUserProfile(): string;
|
|
70
|
+
/** Update the user profile, truncating to max chars */
|
|
71
|
+
export declare function updateUserProfile(content: string): void;
|
|
72
|
+
/** Format user profile for system prompt injection */
|
|
73
|
+
export declare function userProfileToPrompt(): string;
|
|
56
74
|
/**
|
|
57
75
|
* Detect if recent assistant messages contain learnable patterns.
|
|
58
76
|
* Returns structured memories to save, or empty array.
|
|
59
77
|
*/
|
|
60
78
|
export declare function detectMemories(provider: Provider, recentMessages: Message[], model?: string): Promise<Array<{
|
|
61
79
|
name: string;
|
|
62
|
-
type: MemoryEntry[
|
|
80
|
+
type: MemoryEntry["type"];
|
|
63
81
|
description: string;
|
|
64
82
|
content: string;
|
|
65
83
|
}>>;
|