Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
5b871f5
feat: add Claude Code subprocess LLM auth mode
Mar 31, 2026
1e3a89f
fix: address PR review findings for claude-code LLM auth
Mar 31, 2026
d5f7c53
fix: address PR review critical/important issues in claude-code LLM c…
Mar 31, 2026
e949e0f
fix: address second-round PR review issues in claude-code LLM client
Mar 31, 2026
9b616a9
fix: address third-round PR review issues in claude-code LLM client
Mar 31, 2026
0d153b0
fix: resolve dangling variable ReferenceError in smart extraction ini…
Mar 31, 2026
760757c
fix: polish claude-code client — cache queryFn, fix label in cachedSd…
Mar 31, 2026
0261dd4
refactor: simplify claude-code LLM client code
Mar 31, 2026
494f7e0
fix: remove duplicate memory_compact tool registration (refactor resi…
Mar 31, 2026
b363bb1
fix: address silent failure and error handling issues in claude-code …
Mar 31, 2026
1bf8d70
fix: remove stray closing brace left over from memory_compact removal
Mar 31, 2026
350a4e9
test: expand claude-code client test coverage for PR review findings
Mar 31, 2026
c7625d8
fix+test: guard empty-path from which, add auth-routing and no-warn t…
Mar 31, 2026
0b32530
fix: guard stateDir fallback to prevent root-path claude-code-sessions
Mar 31, 2026
0785725
fix: read ~/.claude/settings.json env for Claude Code OAuth tokens
Mar 31, 2026
d71e54e
fix: check constructed env object for auth warning instead of process…
Mar 31, 2026
26d699e
fix: read ~/.claude/settings.json for Claude Code OAuth tokens
Mar 31, 2026
3921647
fix: address critical PR review findings
Mar 31, 2026
8d1f288
fix: address critical and important PR review findings (Round 1)
Mar 31, 2026
8f59cab
fix: address remaining important PR review findings (Round 2)
Mar 31, 2026
2fc7642
fix: OAuth double response-body read + add settings.json and stateDir…
Mar 31, 2026
f49d32a
fix: address round 3 PR review findings
Mar 31, 2026
78e15df
fix: address PR review round 4 findings
Apr 1, 2026
5300466
fix: hoist envAuthPriority out of env loop; preserve specific lastErr…
Apr 1, 2026
3eda4f8
test: add coverage for ENV_AUTH_PRIORITY, extractTextFromSdkMessage, …
Apr 1, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
146 changes: 79 additions & 67 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,18 @@ interface PluginConfig {
// Smart extraction config
smartExtraction?: boolean;
llm?: {
auth?: "api-key" | "oauth";
/**
* LLM provider auth type.
* - "api-key" (default): OpenAI-compatible endpoint; requires llm.apiKey or falls back to embedding.apiKey.
* - "claude-code": Spawn a local Claude Code subprocess via @anthropic-ai/claude-agent-sdk.
* No apiKey/baseURL needed; uses ambient Claude Code auth (subscription or ANTHROPIC_API_KEY env var).
* Prerequisites: Claude Code installed (`npm i -g @anthropic-ai/claude-code`) and
* @anthropic-ai/claude-agent-sdk installed in the plugin (`npm i @anthropic-ai/claude-agent-sdk`).
* - "oauth": OAuth-based endpoint (e.g. ChatGPT via openrouter OAuth).
*/
auth?: "api-key" | "claude-code" | "oauth";
/** Path to the claude executable. Only used when auth="claude-code". Auto-detected via PATH if not set. */
claudeCodePath?: string;
apiKey?: string;
model?: string;
baseURL?: string;
Expand Down Expand Up @@ -294,6 +305,61 @@ function resolveLlmTimeoutMs(config: PluginConfig): number {
return parsePositiveInt(config.llm?.timeoutMs) ?? 30000;
}

/**
* Resolve the LlmClientConfig fields from PluginConfig.
* Centralised to avoid duplicating the same auth-branching logic in multiple call sites.
*/
function resolveLlmClientConfig(
config: PluginConfig,
api: Pick<OpenClawPluginApi, "resolvePath" | "logger">,
): Parameters<typeof createLlmClient>[0] {
const llmAuth = config.llm?.auth || "api-key";

let apiKey: string | undefined;
let baseURL: string | undefined;
let model: string;
let oauthPath: string | undefined;
let oauthProvider: string | undefined;
let claudeCodePath: string | undefined;

switch (llmAuth) {
case "claude-code":
apiKey = config.llm?.apiKey ? resolveEnvVars(config.llm.apiKey) : undefined;
model = config.llm?.model || "claude-sonnet-4-5";
claudeCodePath = config.llm?.claudeCodePath;
break;
case "oauth":
baseURL = config.llm?.baseURL ? resolveEnvVars(config.llm.baseURL) : undefined;
model = config.llm?.model || "openai/gpt-oss-120b";
oauthPath = resolveOptionalPathWithEnv(api, config.llm?.oauthPath, ".memory-lancedb-pro/oauth.json");
oauthProvider = config.llm?.oauthProvider;
break;
default: // "api-key"
apiKey = config.llm?.apiKey
? resolveEnvVars(config.llm.apiKey)
: resolveFirstApiKey(config.embedding.apiKey);
baseURL = config.llm?.baseURL
? resolveEnvVars(config.llm.baseURL)
: config.embedding.baseURL;
model = config.llm?.model || "openai/gpt-oss-120b";
break;
}

return {
auth: llmAuth,
apiKey,
model,
baseURL,
oauthProvider,
oauthPath,
claudeCodePath,
stateDir: api.resolvePath("."),
timeoutMs: resolveLlmTimeoutMs(config),
log: (msg: string) => api.logger.debug(msg),
logWarn: (msg: string) => api.logger.warn(msg),
};
}

function resolveHookAgentId(
explicitAgentId: string | undefined,
sessionKey: string | undefined,
Expand Down Expand Up @@ -1683,45 +1749,19 @@ const memoryLanceDBProPlugin = {

// Initialize smart extraction
let smartExtractor: SmartExtractor | null = null;
let resolvedLlmClient: ReturnType<typeof createLlmClient> | undefined;
if (config.smartExtraction !== false) {
try {
const llmAuth = config.llm?.auth || "api-key";
const llmApiKey = llmAuth === "oauth"
? undefined
: config.llm?.apiKey
? resolveEnvVars(config.llm.apiKey)
: resolveFirstApiKey(config.embedding.apiKey);
const llmBaseURL = llmAuth === "oauth"
? (config.llm?.baseURL ? resolveEnvVars(config.llm.baseURL) : undefined)
: config.llm?.baseURL
? resolveEnvVars(config.llm.baseURL)
: config.embedding.baseURL;
const llmModel = config.llm?.model || "openai/gpt-oss-120b";
const llmOauthPath = llmAuth === "oauth"
? resolveOptionalPathWithEnv(api, config.llm?.oauthPath, ".memory-lancedb-pro/oauth.json")
: undefined;
const llmOauthProvider = llmAuth === "oauth"
? config.llm?.oauthProvider
: undefined;
const llmTimeoutMs = resolveLlmTimeoutMs(config);

const llmClient = createLlmClient({
auth: llmAuth,
apiKey: llmApiKey,
model: llmModel,
baseURL: llmBaseURL,
oauthProvider: llmOauthProvider,
oauthPath: llmOauthPath,
timeoutMs: llmTimeoutMs,
log: (msg: string) => api.logger.debug(msg),
});
const resolvedLlmConfig = resolveLlmClientConfig(config, api);
const llmClient = createLlmClient(resolvedLlmConfig);
resolvedLlmClient = llmClient;

// Initialize embedding-based noise prototype bank (async, non-blocking)
const noiseBank = new NoisePrototypeBank(
(msg: string) => api.logger.debug(msg),
);
noiseBank.init(embedder).catch((err) =>
api.logger.debug(`memory-lancedb-pro: noise bank init: ${String(err)}`),
api.logger.warn(`memory-lancedb-pro: noise bank init failed: ${err instanceof Error ? err.message : String(err)}`),
);

const admissionRejectionAuditWriter = createAdmissionRejectionAuditWriter(
Expand All @@ -1745,13 +1785,16 @@ const memoryLanceDBProPlugin = {

(isCliMode() ? api.logger.debug : api.logger.info)(
"memory-lancedb-pro: smart extraction enabled (LLM model: "
+ llmModel
+ resolvedLlmConfig.model
+ ", timeoutMs: "
+ llmTimeoutMs
+ resolvedLlmConfig.timeoutMs
+ ", noise bank: ON)",
);
} catch (err) {
api.logger.warn(`memory-lancedb-pro: smart extraction init failed, falling back to regex: ${String(err)}`);
// Clear resolvedLlmClient so CLI doesn't attempt to use a potentially broken client
resolvedLlmClient = undefined;
const authMode = config.llm?.auth || "api-key";
api.logger.warn(`memory-lancedb-pro: smart extraction init failed (auth=${authMode}), LLM extraction disabled — falling back to regex only: ${err instanceof Error ? (err.stack ?? err.message) : String(err)}`);
}
}

Expand Down Expand Up @@ -2185,38 +2228,7 @@ const memoryLanceDBProPlugin = {
scopeManager,
migrator,
embedder,
llmClient: smartExtractor ? (() => {
try {
const llmAuth = config.llm?.auth || "api-key";
const llmApiKey = llmAuth === "oauth"
? undefined
: config.llm?.apiKey
? resolveEnvVars(config.llm.apiKey)
: resolveFirstApiKey(config.embedding.apiKey);
const llmBaseURL = llmAuth === "oauth"
? (config.llm?.baseURL ? resolveEnvVars(config.llm.baseURL) : undefined)
: config.llm?.baseURL
? resolveEnvVars(config.llm.baseURL)
: config.embedding.baseURL;
const llmOauthPath = llmAuth === "oauth"
? resolveOptionalPathWithEnv(api, config.llm?.oauthPath, ".memory-lancedb-pro/oauth.json")
: undefined;
const llmOauthProvider = llmAuth === "oauth"
? config.llm?.oauthProvider
: undefined;
const llmTimeoutMs = resolveLlmTimeoutMs(config);
return createLlmClient({
auth: llmAuth,
apiKey: llmApiKey,
model: config.llm?.model || "openai/gpt-oss-120b",
baseURL: llmBaseURL,
oauthProvider: llmOauthProvider,
oauthPath: llmOauthPath,
timeoutMs: llmTimeoutMs,
log: (msg: string) => api.logger.debug(msg),
});
} catch { return undefined; }
})() : undefined,
llmClient: resolvedLlmClient,
}),
{ commands: ["memory-pro"] },
);
Expand Down
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,16 @@
]
},
"scripts": {
"test": "node test/embedder-error-hints.test.mjs && node test/cjk-recursion-regression.test.mjs && node test/migrate-legacy-schema.test.mjs && node --test test/config-session-strategy-migration.test.mjs && node --test test/scope-access-undefined.test.mjs && node --test test/reflection-bypass-hook.test.mjs && node --test test/smart-extractor-scope-filter.test.mjs && node --test test/store-empty-scope-filter.test.mjs && node --test test/recall-text-cleanup.test.mjs && node test/update-consistency-lancedb.test.mjs && node --test test/strip-envelope-metadata.test.mjs && node test/cli-smoke.mjs && node test/functional-e2e.mjs && node test/retriever-rerank-regression.mjs && node test/smart-memory-lifecycle.mjs && node test/smart-extractor-branches.mjs && node test/plugin-manifest-regression.mjs && node --test test/session-summary-before-reset.test.mjs && node --test test/sync-plugin-version.test.mjs && node test/smart-metadata-v2.mjs && node test/vector-search-cosine.test.mjs && node test/context-support-e2e.mjs && node test/temporal-facts.test.mjs && node test/memory-update-supersede.test.mjs && node test/memory-upgrader-diagnostics.test.mjs && node --test test/llm-api-key-client.test.mjs && node --test test/llm-oauth-client.test.mjs && node --test test/cli-oauth-login.test.mjs && node --test test/workflow-fork-guards.test.mjs && node --test test/clawteam-scope.test.mjs && node --test test/cross-process-lock.test.mjs && node --test test/preference-slots.test.mjs",
"test": "node test/embedder-error-hints.test.mjs && node test/cjk-recursion-regression.test.mjs && node test/migrate-legacy-schema.test.mjs && node --test test/config-session-strategy-migration.test.mjs && node --test test/scope-access-undefined.test.mjs && node --test test/reflection-bypass-hook.test.mjs && node --test test/smart-extractor-scope-filter.test.mjs && node --test test/store-empty-scope-filter.test.mjs && node --test test/recall-text-cleanup.test.mjs && node test/update-consistency-lancedb.test.mjs && node --test test/strip-envelope-metadata.test.mjs && node test/cli-smoke.mjs && node test/functional-e2e.mjs && node test/retriever-rerank-regression.mjs && node test/smart-memory-lifecycle.mjs && node test/smart-extractor-branches.mjs && node test/plugin-manifest-regression.mjs && node --test test/session-summary-before-reset.test.mjs && node --test test/sync-plugin-version.test.mjs && node test/smart-metadata-v2.mjs && node test/vector-search-cosine.test.mjs && node test/context-support-e2e.mjs && node test/temporal-facts.test.mjs && node test/memory-update-supersede.test.mjs && node test/memory-upgrader-diagnostics.test.mjs && node --test test/llm-api-key-client.test.mjs && node --test test/llm-oauth-client.test.mjs && node --test test/cli-oauth-login.test.mjs && node --test test/workflow-fork-guards.test.mjs && node --test test/clawteam-scope.test.mjs && node --test test/cross-process-lock.test.mjs && node --test test/preference-slots.test.mjs && node --test test/llm-claude-code-client.test.mjs",
"test:openclaw-host": "node test/openclaw-host-functional.mjs",
"version": "node scripts/sync-plugin-version.mjs openclaw.plugin.json package.json && git add openclaw.plugin.json"
},
"devDependencies": {
"commander": "^14.0.0",
"jiti": "^2.6.0",
"typescript": "^5.9.3"
},
"optionalDependencies": {
"@anthropic-ai/claude-agent-sdk": "^0.2.88"
}
}
Loading
Loading