@codemieai/code 0.10.0 → 0.11.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/dist/agents/core/AgentCLI.d.ts.map +1 -1
- package/dist/agents/core/AgentCLI.js +11 -3
- package/dist/agents/core/AgentCLI.js.map +1 -1
- package/dist/agents/core/BaseAgentAdapter.d.ts +3 -1
- package/dist/agents/core/BaseAgentAdapter.d.ts.map +1 -1
- package/dist/agents/core/BaseAgentAdapter.js +34 -2
- package/dist/agents/core/BaseAgentAdapter.js.map +1 -1
- package/dist/agents/core/config-redaction.d.ts +7 -0
- package/dist/agents/core/config-redaction.d.ts.map +1 -0
- package/dist/agents/core/config-redaction.js +20 -0
- package/dist/agents/core/config-redaction.js.map +1 -0
- package/dist/agents/core/print-config.d.ts +8 -0
- package/dist/agents/core/print-config.d.ts.map +1 -0
- package/dist/agents/core/print-config.js +17 -0
- package/dist/agents/core/print-config.js.map +1 -0
- package/dist/agents/core/session/BaseSessionAdapter.d.ts +5 -1
- package/dist/agents/core/session/BaseSessionAdapter.d.ts.map +1 -1
- package/dist/agents/core/types.d.ts +34 -4
- package/dist/agents/core/types.d.ts.map +1 -1
- package/dist/agents/plugins/claude/claude.plugin.d.ts.map +1 -1
- package/dist/agents/plugins/claude/claude.plugin.js +73 -0
- package/dist/agents/plugins/claude/claude.plugin.js.map +1 -1
- package/dist/agents/plugins/claude/session/processors/claude.conversations-processor.d.ts.map +1 -1
- package/dist/agents/plugins/claude/session/processors/claude.conversations-processor.js +115 -32
- package/dist/agents/plugins/claude/session/processors/claude.conversations-processor.js.map +1 -1
- package/dist/agents/plugins/claude/settings-conflict.d.ts +8 -0
- package/dist/agents/plugins/claude/settings-conflict.d.ts.map +1 -0
- package/dist/agents/plugins/claude/settings-conflict.js +35 -0
- package/dist/agents/plugins/claude/settings-conflict.js.map +1 -0
- package/dist/agents/plugins/codemie-code-hooks/shell-hooks-source.d.ts +14 -2
- package/dist/agents/plugins/codemie-code-hooks/shell-hooks-source.d.ts.map +1 -1
- package/dist/agents/plugins/codemie-code-hooks/shell-hooks-source.js +218 -263
- package/dist/agents/plugins/codemie-code-hooks/shell-hooks-source.js.map +1 -1
- package/dist/agents/plugins/opencode/opencode-message-types.d.ts +13 -0
- package/dist/agents/plugins/opencode/opencode-message-types.d.ts.map +1 -1
- package/dist/agents/plugins/opencode/opencode-message-types.js.map +1 -1
- package/dist/agents/plugins/opencode/opencode.incremental-sync.d.ts +38 -0
- package/dist/agents/plugins/opencode/opencode.incremental-sync.d.ts.map +1 -0
- package/dist/agents/plugins/opencode/opencode.incremental-sync.js +153 -0
- package/dist/agents/plugins/opencode/opencode.incremental-sync.js.map +1 -0
- package/dist/agents/plugins/opencode/opencode.paths.d.ts +18 -2
- package/dist/agents/plugins/opencode/opencode.paths.d.ts.map +1 -1
- package/dist/agents/plugins/opencode/opencode.paths.js +39 -6
- package/dist/agents/plugins/opencode/opencode.paths.js.map +1 -1
- package/dist/agents/plugins/opencode/opencode.plugin.d.ts.map +1 -1
- package/dist/agents/plugins/opencode/opencode.plugin.js +241 -75
- package/dist/agents/plugins/opencode/opencode.plugin.js.map +1 -1
- package/dist/agents/plugins/opencode/opencode.session.d.ts +18 -1
- package/dist/agents/plugins/opencode/opencode.session.d.ts.map +1 -1
- package/dist/agents/plugins/opencode/opencode.session.js +58 -17
- package/dist/agents/plugins/opencode/opencode.session.js.map +1 -1
- package/dist/agents/plugins/opencode/opencode.sqlite-reader.d.ts +8 -2
- package/dist/agents/plugins/opencode/opencode.sqlite-reader.d.ts.map +1 -1
- package/dist/agents/plugins/opencode/opencode.sqlite-reader.js +67 -42
- package/dist/agents/plugins/opencode/opencode.sqlite-reader.js.map +1 -1
- package/dist/agents/plugins/opencode/opencode.storage-utils.d.ts +19 -0
- package/dist/agents/plugins/opencode/opencode.storage-utils.d.ts.map +1 -1
- package/dist/agents/plugins/opencode/opencode.storage-utils.js +51 -0
- package/dist/agents/plugins/opencode/opencode.storage-utils.js.map +1 -1
- package/dist/agents/plugins/opencode/session/processors/opencode.conversations-processor.d.ts +13 -19
- package/dist/agents/plugins/opencode/session/processors/opencode.conversations-processor.d.ts.map +1 -1
- package/dist/agents/plugins/opencode/session/processors/opencode.conversations-processor.js +339 -82
- package/dist/agents/plugins/opencode/session/processors/opencode.conversations-processor.js.map +1 -1
- package/dist/agents/plugins/opencode/session/processors/opencode.metrics-processor.d.ts +37 -33
- package/dist/agents/plugins/opencode/session/processors/opencode.metrics-processor.d.ts.map +1 -1
- package/dist/agents/plugins/opencode/session/processors/opencode.metrics-processor.js +204 -246
- package/dist/agents/plugins/opencode/session/processors/opencode.metrics-processor.js.map +1 -1
- package/dist/cli/commands/proxy/connectors/desktop.d.ts.map +1 -1
- package/dist/cli/commands/proxy/connectors/desktop.js +6 -0
- package/dist/cli/commands/proxy/connectors/desktop.js.map +1 -1
- package/dist/cli/commands/proxy/connectors/vscode-models.d.ts +21 -0
- package/dist/cli/commands/proxy/connectors/vscode-models.d.ts.map +1 -0
- package/dist/cli/commands/proxy/connectors/vscode-models.js +286 -0
- package/dist/cli/commands/proxy/connectors/vscode-models.js.map +1 -0
- package/dist/cli/commands/proxy/connectors/vscode.d.ts +2 -2
- package/dist/cli/commands/proxy/connectors/vscode.d.ts.map +1 -1
- package/dist/cli/commands/proxy/connectors/vscode.js +47 -66
- package/dist/cli/commands/proxy/connectors/vscode.js.map +1 -1
- package/dist/cli/commands/proxy/health-check.d.ts.map +1 -1
- package/dist/cli/commands/proxy/health-check.js +9 -0
- package/dist/cli/commands/proxy/health-check.js.map +1 -1
- package/dist/cli/commands/proxy/index.d.ts.map +1 -1
- package/dist/cli/commands/proxy/index.js +10 -13
- package/dist/cli/commands/proxy/index.js.map +1 -1
- package/dist/cli/commands/setup.d.ts +31 -0
- package/dist/cli/commands/setup.d.ts.map +1 -1
- package/dist/cli/commands/setup.js +137 -45
- package/dist/cli/commands/setup.js.map +1 -1
- package/dist/providers/core/types.d.ts +12 -1
- package/dist/providers/core/types.d.ts.map +1 -1
- package/dist/providers/core/types.js.map +1 -1
- package/dist/providers/plugins/bedrock/bedrock.template.d.ts.map +1 -1
- package/dist/providers/plugins/bedrock/bedrock.template.js +21 -2
- package/dist/providers/plugins/bedrock/bedrock.template.js.map +1 -1
- package/dist/providers/plugins/litellm/litellm.setup-steps.d.ts.map +1 -1
- package/dist/providers/plugins/litellm/litellm.setup-steps.js +21 -5
- package/dist/providers/plugins/litellm/litellm.setup-steps.js.map +1 -1
- package/dist/providers/plugins/sso/proxy/plugins/codex-encrypted-content-sanitizer.plugin.d.ts +7 -6
- package/dist/providers/plugins/sso/proxy/plugins/codex-encrypted-content-sanitizer.plugin.d.ts.map +1 -1
- package/dist/providers/plugins/sso/proxy/plugins/codex-encrypted-content-sanitizer.plugin.js +13 -7
- package/dist/providers/plugins/sso/proxy/plugins/codex-encrypted-content-sanitizer.plugin.js.map +1 -1
- package/dist/providers/plugins/sso/proxy/plugins/index.d.ts +2 -1
- package/dist/providers/plugins/sso/proxy/plugins/index.d.ts.map +1 -1
- package/dist/providers/plugins/sso/proxy/plugins/index.js +4 -2
- package/dist/providers/plugins/sso/proxy/plugins/index.js.map +1 -1
- package/dist/providers/plugins/sso/proxy/plugins/vscode-request-normalizer.plugin.d.ts +22 -0
- package/dist/providers/plugins/sso/proxy/plugins/vscode-request-normalizer.plugin.d.ts.map +1 -0
- package/dist/providers/plugins/sso/proxy/plugins/vscode-request-normalizer.plugin.js +69 -0
- package/dist/providers/plugins/sso/proxy/plugins/vscode-request-normalizer.plugin.js.map +1 -0
- package/dist/providers/plugins/sso/session/processors/conversations/syncProcessor.js +3 -0
- package/dist/providers/plugins/sso/session/processors/conversations/syncProcessor.js.map +1 -1
- package/dist/utils/config.d.ts +6 -2
- package/dist/utils/config.d.ts.map +1 -1
- package/dist/utils/config.js +30 -8
- package/dist/utils/config.js.map +1 -1
- package/dist/utils/extensions-scan.d.ts.map +1 -1
- package/dist/utils/extensions-scan.js +59 -22
- package/dist/utils/extensions-scan.js.map +1 -1
- package/dist/utils/mcp-config.d.ts.map +1 -1
- package/dist/utils/mcp-config.js +11 -6
- package/dist/utils/mcp-config.js.map +1 -1
- package/package.json +2 -1
|
@@ -8,8 +8,20 @@
|
|
|
8
8
|
* (Anthropic/Claude Code format) and maps them to OpenCode plugin lifecycle hooks.
|
|
9
9
|
*
|
|
10
10
|
* Why a string constant: The plugin uses `import type { Plugin } from "@opencode-ai/plugin"`
|
|
11
|
-
* which doesn't exist in
|
|
11
|
+
* which doesn't exist in this package's dependencies. Embedding as a string avoids
|
|
12
12
|
* TypeScript compilation issues. Bun strips the type import at runtime.
|
|
13
|
+
*
|
|
14
|
+
* PLUGIN CONTRACT — see reasoning-sanitizer-source.ts for the reference shape:
|
|
15
|
+
* a Plugin is an async factory returning a FLAT map of dotted-key handlers, and
|
|
16
|
+
* each handler takes `(input, output)` and MUTATES `output`. Returned values are
|
|
17
|
+
* discarded. An earlier version of this file exported a plain object with nested
|
|
18
|
+
* `hooks: { tool: { execute: { before } } }` keys and returned values from every
|
|
19
|
+
* handler, so none of it ever ran.
|
|
20
|
+
*
|
|
21
|
+
* Session id: taken from the payload OpenCode passes in (`input.sessionID`, or
|
|
22
|
+
* `input.event.properties.sessionID` for bus events). `codemie hook` rejects an
|
|
23
|
+
* empty session_id with exit code 2, so a hook with no resolvable session id is
|
|
24
|
+
* skipped rather than invoked.
|
|
13
25
|
*/
|
|
14
|
-
export declare const SHELL_HOOKS_PLUGIN_SOURCE = "\nimport type { Plugin } from \"@opencode-ai/plugin\";\nimport { execSync, spawn } from \"child_process\";\nimport { readFileSync, existsSync } from \"fs\";\nimport { join } from \"path\";\n\n// \u2500\u2500\u2500 Types \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\ninterface HookConfig {\n type: \"command\" | \"prompt\" | \"agent\";\n command?: string;\n timeout?: number; // seconds\n async?: boolean;\n}\n\ninterface HookMatcherEntry {\n matcher?: string;\n hooks: HookConfig[];\n}\n\ntype HookEventName =\n | \"PreToolUse\"\n | \"PostToolUse\"\n | \"UserPromptSubmit\"\n | \"PermissionRequest\"\n | \"PreCompact\"\n | \"SessionStart\"\n | \"SessionEnd\"\n | \"Stop\"\n | \"Notification\";\n\ninterface HooksConfig {\n hooks?: Partial<Record<HookEventName, HookMatcherEntry[]>>;\n}\n\ninterface HookStdinPayload {\n hook_event_name: string;\n session_id: string;\n cwd: string;\n permission_mode: string;\n transcript_path: string;\n tool_name?: string;\n tool_input?: Record<string, unknown>;\n tool_output?: string;\n prompt?: string;\n [key: string]: unknown;\n}\n\n// \u2500\u2500\u2500 Matcher (ported from codemie-code src/hooks/matcher.ts) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction matchesPattern(pattern: string, toolName: string): boolean {\n try {\n if (!pattern || pattern === \"*\") return true;\n if (/[|[\\]{}()]/.test(pattern)) {\n try {\n return new RegExp(\"^(\" + pattern + \")$\").test(toolName);\n } catch {\n return pattern === toolName;\n }\n }\n return pattern === toolName;\n } catch {\n return false;\n }\n}\n\n// \u2500\u2500\u2500 Config Loading \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction loadHooksConfig(): HooksConfig {\n // Priority 1: OPENCODE_HOOKS env var (set by codemie-code)\n const envHooks = process.env.OPENCODE_HOOKS;\n if (envHooks) {\n try {\n const parsed = JSON.parse(envHooks);\n if (parsed.hooks && Object.keys(parsed.hooks).length > 0) {\n return parsed as HooksConfig;\n }\n } catch {\n // Fall through to file-based config\n }\n }\n\n // Priority 2: .opencode/hooks.json in project directory\n const projectDir = process.env.OPENCODE_PROJECT_DIR || process.cwd();\n const hooksFile = join(projectDir, \".opencode\", \"hooks.json\");\n if (existsSync(hooksFile)) {\n try {\n const content = readFileSync(hooksFile, \"utf-8\");\n const parsed = JSON.parse(content);\n if (parsed.hooks) return parsed as HooksConfig;\n } catch {\n // Ignore parse errors\n }\n }\n\n return { hooks: {} };\n}\n\n// \u2500\u2500\u2500 Hook Resolution \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction getMatchingCommands(\n config: HooksConfig,\n event: HookEventName,\n toolName?: string,\n): Array<{ command: string; timeout: number; isAsync: boolean }> {\n const matchers = config.hooks?.[event];\n if (!matchers || matchers.length === 0) return [];\n\n const result: Array<{ command: string; timeout: number; isAsync: boolean }> = [];\n\n for (const entry of matchers) {\n const pattern = entry.matcher || \"*\";\n const shouldMatch = !toolName || matchesPattern(pattern, toolName);\n if (!shouldMatch) continue;\n\n for (const hook of entry.hooks) {\n // Only support \"command\" type \u2014 skip \"prompt\" and \"agent\"\n if (hook.type !== \"command\" || !hook.command) continue;\n result.push({\n command: hook.command,\n timeout: (hook.timeout || 60) * 1000, // seconds \u2192 ms\n isAsync: hook.async === true,\n });\n }\n }\n\n return result;\n}\n\n// \u2500\u2500\u2500 Shell Execution \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction buildEnvVars(sessionId: string, event: string): Record<string, string> {\n const projectDir = process.env.OPENCODE_PROJECT_DIR || process.cwd();\n return {\n OPENCODE_PROJECT_DIR: projectDir,\n OPENCODE_SESSION_ID: sessionId,\n OPENCODE_HOOK_EVENT: event,\n CLAUDE_PROJECT_DIR: projectDir, // Anthropic alias\n };\n}\n\ninterface ExecResult {\n stdout: string;\n stderr: string;\n exitCode: number;\n}\n\nfunction execCommand(\n command: string,\n stdin: string,\n env: Record<string, string>,\n timeout: number,\n): ExecResult {\n try {\n const stdout = execSync(command, {\n input: stdin,\n timeout,\n env: { ...process.env, ...env },\n encoding: \"utf-8\",\n maxBuffer: 1024 * 1024,\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n });\n return { stdout: stdout || \"\", stderr: \"\", exitCode: 0 };\n } catch (err: any) {\n return {\n stdout: err.stdout || \"\",\n stderr: err.stderr || \"\",\n exitCode: typeof err.status === \"number\" ? err.status : 1,\n };\n }\n}\n\nfunction execCommandAsync(\n command: string,\n stdin: string,\n env: Record<string, string>,\n): void {\n const child = spawn(\"sh\", [\"-c\", command], {\n env: { ...process.env, ...env },\n stdio: [\"pipe\", \"ignore\", \"ignore\"],\n detached: true,\n });\n if (child.stdin) {\n child.stdin.write(stdin);\n child.stdin.end();\n }\n child.unref();\n}\n\n// \u2500\u2500\u2500 Response Parsing \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\ninterface ParsedResponse {\n blocked: boolean;\n reason?: string;\n updatedInput?: Record<string, unknown>;\n permissionDecision?: \"allow\" | \"deny\" | \"ask\";\n additionalContext?: string;\n}\n\nfunction parseResponse(\n result: ExecResult,\n event: HookEventName,\n): ParsedResponse {\n // Exit code 2 = block\n if (result.exitCode === 2) {\n const reason = [result.stderr, result.stdout.trim()].filter(Boolean).join(\"\\n\");\n return { blocked: true, reason: reason || \"Hook blocked execution (exit code 2)\" };\n }\n\n // Non-zero, non-2 = non-blocking error (allow)\n if (result.exitCode !== 0) {\n return { blocked: false };\n }\n\n // Parse stdout JSON\n const trimmed = result.stdout.trim();\n if (!trimmed) return { blocked: false };\n\n try {\n const json = JSON.parse(trimmed);\n\n // PreToolUse: check for hookSpecificOutput.updatedInput or plain object \u2192 merge into args\n if (event === \"PreToolUse\") {\n const updated = json.hookSpecificOutput?.updatedInput || json.updatedInput;\n if (updated && typeof updated === \"object\") {\n return { blocked: false, updatedInput: updated };\n }\n // If it's a plain object without known keys, treat as updatedInput\n if (typeof json === \"object\" && !json.hookSpecificOutput && !json.decision) {\n return { blocked: false, updatedInput: json };\n }\n }\n\n // PermissionRequest: check for permissionDecision\n if (event === \"PermissionRequest\") {\n const decision =\n json.hookSpecificOutput?.permissionDecision || json.permissionDecision;\n if (decision && [\"allow\", \"deny\", \"ask\"].includes(decision)) {\n return { blocked: false, permissionDecision: decision };\n }\n // Plain string\n if (typeof json === \"string\" && [\"allow\", \"deny\", \"ask\"].includes(json)) {\n return { blocked: false, permissionDecision: json };\n }\n }\n\n // PreCompact: additionalContext\n if (event === \"PreCompact\") {\n const context = json.additionalContext || json.context;\n if (typeof context === \"string\") {\n return { blocked: false, additionalContext: context };\n }\n }\n\n return { blocked: false };\n } catch {\n // Non-JSON output \u2014 treat as informational\n return { blocked: false };\n }\n}\n\n// \u2500\u2500\u2500 Plugin Definition \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst config = loadHooksConfig();\nconst hasHooks = config.hooks && Object.keys(config.hooks).length > 0;\n\nconst plugin: Plugin = {\n name: \"shell-hooks\",\n ...(hasHooks\n ? {\n hooks: {\n // PreToolUse \u2192 tool.execute.before (blocking)\n tool: {\n execute: {\n before: async (input) => {\n const commands = getMatchingCommands(config, \"PreToolUse\", input.tool);\n if (commands.length === 0) return input;\n\n const sessionId = process.env.OPENCODE_SESSION_ID || \"\";\n const env = buildEnvVars(sessionId, \"PreToolUse\");\n const payload: HookStdinPayload = {\n hook_event_name: \"PreToolUse\",\n session_id: sessionId,\n cwd: process.cwd(),\n permission_mode: \"default\",\n transcript_path: \"\",\n tool_name: input.tool,\n tool_input: input.args as Record<string, unknown>,\n };\n const stdin = JSON.stringify(payload);\n\n let mergedInput = { ...input };\n for (const cmd of commands) {\n if (cmd.isAsync) {\n execCommandAsync(cmd.command, stdin, env);\n continue;\n }\n const result = execCommand(cmd.command, stdin, env, cmd.timeout);\n const parsed = parseResponse(result, \"PreToolUse\");\n if (parsed.blocked) {\n throw new Error(parsed.reason || \"Hook blocked tool execution\");\n }\n if (parsed.updatedInput) {\n mergedInput = {\n ...mergedInput,\n args: { ...(mergedInput.args as Record<string, unknown>), ...parsed.updatedInput },\n };\n }\n }\n return mergedInput;\n },\n\n // PostToolUse \u2192 tool.execute.after (fire-and-forget)\n after: async (output) => {\n const commands = getMatchingCommands(config, \"PostToolUse\", output.tool);\n if (commands.length === 0) return output;\n\n const sessionId = process.env.OPENCODE_SESSION_ID || \"\";\n const env = buildEnvVars(sessionId, \"PostToolUse\");\n const payload: HookStdinPayload = {\n hook_event_name: \"PostToolUse\",\n session_id: sessionId,\n cwd: process.cwd(),\n permission_mode: \"default\",\n transcript_path: \"\",\n tool_name: output.tool,\n tool_input: output.args as Record<string, unknown>,\n tool_output: typeof output.result === \"string\" ? output.result : JSON.stringify(output.result),\n };\n const stdin = JSON.stringify(payload);\n\n for (const cmd of commands) {\n if (cmd.isAsync) {\n execCommandAsync(cmd.command, stdin, env);\n continue;\n }\n try {\n execCommand(cmd.command, stdin, env, cmd.timeout);\n } catch {\n // PostToolUse is fire-and-forget\n }\n }\n return output;\n },\n },\n },\n\n // UserPromptSubmit \u2192 chat.message (blocking)\n chat: {\n message: async (input) => {\n const commands = getMatchingCommands(config, \"UserPromptSubmit\");\n if (commands.length === 0) return input;\n\n const sessionId = process.env.OPENCODE_SESSION_ID || \"\";\n const env = buildEnvVars(sessionId, \"UserPromptSubmit\");\n const payload: HookStdinPayload = {\n hook_event_name: \"UserPromptSubmit\",\n session_id: sessionId,\n cwd: process.cwd(),\n permission_mode: \"default\",\n transcript_path: \"\",\n prompt: Array.isArray(input.parts)\n ? input.parts\n .filter((p: any) => p.type === \"text\")\n .map((p: any) => p.text)\n .join(\"\\n\")\n : String(input.parts),\n };\n const stdin = JSON.stringify(payload);\n\n for (const cmd of commands) {\n if (cmd.isAsync) {\n execCommandAsync(cmd.command, stdin, env);\n continue;\n }\n const result = execCommand(cmd.command, stdin, env, cmd.timeout);\n const parsed = parseResponse(result, \"UserPromptSubmit\");\n if (parsed.blocked) {\n // Clear message parts to block submission\n return { ...input, parts: [] };\n }\n }\n return input;\n },\n },\n\n // PermissionRequest \u2192 permission.ask (blocking)\n permission: {\n ask: async (input) => {\n const commands = getMatchingCommands(config, \"PermissionRequest\", input.tool);\n if (commands.length === 0) return input;\n\n const sessionId = process.env.OPENCODE_SESSION_ID || \"\";\n const env = buildEnvVars(sessionId, \"PermissionRequest\");\n const payload: HookStdinPayload = {\n hook_event_name: \"PermissionRequest\",\n session_id: sessionId,\n cwd: process.cwd(),\n permission_mode: \"default\",\n transcript_path: \"\",\n tool_name: input.tool,\n tool_input: input.args as Record<string, unknown>,\n };\n const stdin = JSON.stringify(payload);\n\n for (const cmd of commands) {\n if (cmd.isAsync) {\n execCommandAsync(cmd.command, stdin, env);\n continue;\n }\n const result = execCommand(cmd.command, stdin, env, cmd.timeout);\n const parsed = parseResponse(result, \"PermissionRequest\");\n if (parsed.permissionDecision === \"allow\") {\n return { ...input, allowed: true };\n }\n if (parsed.permissionDecision === \"deny\") {\n return { ...input, allowed: false };\n }\n }\n return input;\n },\n },\n\n // PreCompact \u2192 experimental.session.compacting (non-blocking)\n experimental: {\n session: {\n compacting: async (input) => {\n const commands = getMatchingCommands(config, \"PreCompact\");\n if (commands.length === 0) return input;\n\n const sessionId = process.env.OPENCODE_SESSION_ID || \"\";\n const env = buildEnvVars(sessionId, \"PreCompact\");\n const payload: HookStdinPayload = {\n hook_event_name: \"PreCompact\",\n session_id: sessionId,\n cwd: process.cwd(),\n permission_mode: \"default\",\n transcript_path: \"\",\n };\n const stdin = JSON.stringify(payload);\n\n for (const cmd of commands) {\n if (cmd.isAsync) {\n execCommandAsync(cmd.command, stdin, env);\n continue;\n }\n try {\n const result = execCommand(cmd.command, stdin, env, cmd.timeout);\n const parsed = parseResponse(result, \"PreCompact\");\n if (parsed.additionalContext) {\n return {\n ...input,\n context: ((input as any).context || \"\") + \"\\n\" + parsed.additionalContext,\n };\n }\n } catch {\n // Non-blocking\n }\n }\n return input;\n },\n },\n },\n\n // SessionStart/SessionEnd/Stop/Notification \u2192 event (non-blocking)\n event: async (input) => {\n let hookEvent: HookEventName | undefined;\n const eventType = (input as any).type || (input as any).event;\n if (eventType === \"session.created\") hookEvent = \"SessionStart\";\n else if (eventType === \"session.deleted\") hookEvent = \"SessionEnd\";\n else if (eventType === \"session.idle\") hookEvent = \"Stop\";\n else if (eventType === \"session.error\") hookEvent = \"Notification\";\n\n if (!hookEvent) return;\n\n const commands = getMatchingCommands(config, hookEvent);\n if (commands.length === 0) return;\n\n const sessionId = process.env.OPENCODE_SESSION_ID || \"\";\n const env = buildEnvVars(sessionId, hookEvent);\n const payload: HookStdinPayload = {\n hook_event_name: hookEvent,\n session_id: sessionId,\n cwd: process.cwd(),\n permission_mode: \"default\",\n transcript_path: \"\",\n };\n const stdin = JSON.stringify(payload);\n\n for (const cmd of commands) {\n if (cmd.isAsync) {\n execCommandAsync(cmd.command, stdin, env);\n continue;\n }\n try {\n execCommand(cmd.command, stdin, env, cmd.timeout);\n } catch {\n // Event hooks are non-blocking\n }\n }\n },\n },\n }\n : {}),\n};\n\nexport default plugin;\n";
|
|
26
|
+
export declare const SHELL_HOOKS_PLUGIN_SOURCE = "\nimport type { Plugin } from \"@opencode-ai/plugin\";\nimport { execSync, spawn } from \"child_process\";\nimport { readFileSync, existsSync } from \"fs\";\nimport { join } from \"path\";\n\n// \u2500\u2500\u2500 Types \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\ninterface HookConfig {\n type: \"command\" | \"prompt\" | \"agent\";\n command?: string;\n timeout?: number; // seconds\n async?: boolean;\n}\n\ninterface HookMatcherEntry {\n matcher?: string;\n hooks: HookConfig[];\n}\n\ntype HookEventName =\n | \"PreToolUse\"\n | \"PostToolUse\"\n | \"UserPromptSubmit\"\n | \"PreCompact\"\n | \"SessionStart\"\n | \"SessionEnd\"\n | \"Stop\"\n | \"Notification\";\n\ninterface HooksConfig {\n hooks?: Partial<Record<HookEventName, HookMatcherEntry[]>>;\n}\n\ninterface HookStdinPayload {\n hook_event_name: string;\n session_id: string;\n cwd: string;\n permission_mode: string;\n transcript_path: string;\n tool_name?: string;\n tool_input?: Record<string, unknown>;\n tool_output?: string;\n prompt?: string;\n [key: string]: unknown;\n}\n\n// \u2500\u2500\u2500 Matcher (ported from codemie-code src/hooks/matcher.ts) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction matchesPattern(pattern: string, toolName: string): boolean {\n try {\n if (!pattern || pattern === \"*\") return true;\n if (/[|[\\]{}()]/.test(pattern)) {\n try {\n return new RegExp(\"^(\" + pattern + \")$\").test(toolName);\n } catch {\n return pattern === toolName;\n }\n }\n return pattern === toolName;\n } catch {\n return false;\n }\n}\n\n// \u2500\u2500\u2500 Config Loading \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction loadHooksConfig(): HooksConfig {\n // Priority 1: OPENCODE_HOOKS env var (set by the CodeMie CLI)\n const envHooks = process.env.OPENCODE_HOOKS;\n if (envHooks) {\n try {\n const parsed = JSON.parse(envHooks);\n if (parsed.hooks && Object.keys(parsed.hooks).length > 0) {\n return parsed as HooksConfig;\n }\n } catch {\n // Fall through to file-based config\n }\n }\n\n // Priority 2: .opencode/hooks.json in project directory\n const projectDir = process.env.OPENCODE_PROJECT_DIR || process.cwd();\n const hooksFile = join(projectDir, \".opencode\", \"hooks.json\");\n if (existsSync(hooksFile)) {\n try {\n const content = readFileSync(hooksFile, \"utf-8\");\n const parsed = JSON.parse(content);\n if (parsed.hooks) return parsed as HooksConfig;\n } catch {\n // Ignore parse errors\n }\n }\n\n return { hooks: {} };\n}\n\n// \u2500\u2500\u2500 Hook Resolution \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction getMatchingCommands(\n config: HooksConfig,\n event: HookEventName,\n toolName?: string,\n): Array<{ command: string; timeout: number; isAsync: boolean }> {\n const matchers = config.hooks?.[event];\n if (!matchers || matchers.length === 0) return [];\n\n const result: Array<{ command: string; timeout: number; isAsync: boolean }> = [];\n\n for (const entry of matchers) {\n const pattern = entry.matcher || \"*\";\n const shouldMatch = !toolName || matchesPattern(pattern, toolName);\n if (!shouldMatch) continue;\n\n for (const hook of entry.hooks) {\n // Only support \"command\" type \u2014 skip \"prompt\" and \"agent\"\n if (hook.type !== \"command\" || !hook.command) continue;\n result.push({\n command: hook.command,\n timeout: (hook.timeout || 60) * 1000, // seconds \u2192 ms\n isAsync: hook.async === true,\n });\n }\n }\n\n return result;\n}\n\n// \u2500\u2500\u2500 Session / Payload Helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n// OPENCODE_SESSION_ID is a last-resort fallback only: nothing sets it today, and\n// relying on it is what made every previous hook invocation ship an empty\n// session_id and get rejected with exit code 2.\nfunction resolveSessionId(input: any): string {\n return (\n input?.sessionID ||\n input?.event?.properties?.sessionID ||\n input?.event?.properties?.info?.id ||\n process.env.OPENCODE_SESSION_ID ||\n \"\"\n );\n}\n\n// codemie hook needs a transcript path to re-parse the session; for OpenCode\n// that is the SQLite database, exported by the CLI before spawning.\nfunction transcriptPath(): string {\n return process.env.CODEMIE_OPENCODE_TRANSCRIPT || \"\";\n}\n\nfunction buildEnvVars(sessionId: string, event: string): Record<string, string> {\n const projectDir = process.env.OPENCODE_PROJECT_DIR || process.cwd();\n return {\n OPENCODE_PROJECT_DIR: projectDir,\n OPENCODE_SESSION_ID: sessionId,\n OPENCODE_HOOK_EVENT: event,\n CLAUDE_PROJECT_DIR: projectDir, // Anthropic alias\n };\n}\n\nfunction basePayload(event: HookEventName, sessionId: string): HookStdinPayload {\n return {\n hook_event_name: event,\n session_id: sessionId,\n cwd: process.cwd(),\n permission_mode: \"default\",\n transcript_path: transcriptPath(),\n };\n}\n\n// \u2500\u2500\u2500 Shell Execution \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\ninterface ExecResult {\n stdout: string;\n stderr: string;\n exitCode: number;\n}\n\nfunction execCommand(\n command: string,\n stdin: string,\n env: Record<string, string>,\n timeout: number,\n): ExecResult {\n try {\n const stdout = execSync(command, {\n input: stdin,\n timeout,\n env: { ...process.env, ...env },\n encoding: \"utf-8\",\n maxBuffer: 1024 * 1024,\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n });\n return { stdout: stdout || \"\", stderr: \"\", exitCode: 0 };\n } catch (err: any) {\n return {\n stdout: err.stdout || \"\",\n stderr: err.stderr || \"\",\n exitCode: typeof err.status === \"number\" ? err.status : 1,\n };\n }\n}\n\nfunction execCommandAsync(\n command: string,\n stdin: string,\n env: Record<string, string>,\n): void {\n const child = spawn(\"sh\", [\"-c\", command], {\n env: { ...process.env, ...env },\n stdio: [\"pipe\", \"ignore\", \"ignore\"],\n detached: true,\n });\n // A ChildProcess or stdin 'error' with no listener is an UNHANDLED error\n // event, which throws inside the host OpenCode process. Stop is configured\n // async, so this is the production path: a failed spawn (ENOENT for sh,\n // EAGAIN) or a broken pipe must degrade telemetry, never kill the session.\n child.on(\"error\", () => {});\n if (child.stdin) {\n child.stdin.on(\"error\", () => {});\n child.stdin.write(stdin);\n child.stdin.end();\n }\n child.unref();\n}\n\n// \u2500\u2500\u2500 Response Parsing \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\ninterface ParsedResponse {\n blocked: boolean;\n reason?: string;\n updatedInput?: Record<string, unknown>;\n additionalContext?: string;\n}\n\nfunction parseResponse(\n result: ExecResult,\n event: HookEventName,\n): ParsedResponse {\n // Exit code 2 = block\n if (result.exitCode === 2) {\n const reason = [result.stderr, result.stdout.trim()].filter(Boolean).join(\"\\n\");\n return { blocked: true, reason: reason || \"Hook blocked execution (exit code 2)\" };\n }\n\n // Non-zero, non-2 = non-blocking error (allow)\n if (result.exitCode !== 0) {\n return { blocked: false };\n }\n\n const trimmed = result.stdout.trim();\n if (!trimmed) return { blocked: false };\n\n try {\n const json = JSON.parse(trimmed);\n\n // PreToolUse: hookSpecificOutput.updatedInput, or a bare object \u2192 merged args\n if (event === \"PreToolUse\") {\n const updated = json.hookSpecificOutput?.updatedInput || json.updatedInput;\n if (updated && typeof updated === \"object\") {\n return { blocked: false, updatedInput: updated };\n }\n if (typeof json === \"object\" && !json.hookSpecificOutput && !json.decision) {\n return { blocked: false, updatedInput: json };\n }\n }\n\n if (event === \"PreCompact\") {\n const context = json.additionalContext || json.context;\n if (typeof context === \"string\") {\n return { blocked: false, additionalContext: context };\n }\n }\n\n return { blocked: false };\n } catch {\n // Non-JSON output \u2014 treat as informational\n return { blocked: false };\n }\n}\n\n/** Run every configured command for an event. Returns the parsed responses. */\nfunction runCommands(\n event: HookEventName,\n sessionId: string,\n payload: HookStdinPayload,\n commands: Array<{ command: string; timeout: number; isAsync: boolean }>,\n swallowErrors: boolean,\n): ParsedResponse[] {\n const env = buildEnvVars(sessionId, event);\n const stdin = JSON.stringify(payload);\n const responses: ParsedResponse[] = [];\n\n for (const cmd of commands) {\n if (cmd.isAsync) {\n execCommandAsync(cmd.command, stdin, env);\n continue;\n }\n if (swallowErrors) {\n try {\n responses.push(parseResponse(execCommand(cmd.command, stdin, env, cmd.timeout), event));\n } catch {\n // Fire-and-forget events must never break the session.\n }\n continue;\n }\n responses.push(parseResponse(execCommand(cmd.command, stdin, env, cmd.timeout), event));\n }\n\n return responses;\n}\n\n// \u2500\u2500\u2500 Idle Deduplication \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n// OpenCode publishes both the deprecated `session.idle` and the newer\n// `session.status` {type:\"idle\"} back to back. Subscribing to only one risks\n// silently losing active-time accounting if it is removed upstream, so both are\n// handled and collapsed on the busy\u2192idle transition.\nconst idleState = new Map<string, boolean>();\n\nfunction shouldEmitIdle(sessionId: string, isIdle: boolean): boolean {\n const wasIdle = idleState.get(sessionId) === true;\n idleState.set(sessionId, isIdle);\n return isIdle && !wasIdle;\n}\n\n// \u2500\u2500\u2500 Plugin Definition \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst config = loadHooksConfig();\n\nconst ShellHooksPlugin: Plugin = async (_input) => ({\n // PreToolUse \u2192 tool.execute.before (blocking; mutate output.args)\n \"tool.execute.before\": async (input: any, output: any) => {\n const commands = getMatchingCommands(config, \"PreToolUse\", input?.tool);\n if (commands.length === 0) return;\n\n const sessionId = resolveSessionId(input);\n if (!sessionId) return;\n\n const payload = basePayload(\"PreToolUse\", sessionId);\n payload.tool_name = input?.tool;\n payload.tool_input = output?.args as Record<string, unknown>;\n\n for (const parsed of runCommands(\"PreToolUse\", sessionId, payload, commands, false)) {\n if (parsed.blocked) {\n throw new Error(parsed.reason || \"Hook blocked tool execution\");\n }\n if (parsed.updatedInput && output) {\n output.args = { ...(output.args || {}), ...parsed.updatedInput };\n }\n }\n },\n\n // PostToolUse \u2192 tool.execute.after (fire-and-forget)\n \"tool.execute.after\": async (input: any, output: any) => {\n const commands = getMatchingCommands(config, \"PostToolUse\", input?.tool);\n if (commands.length === 0) return;\n\n const sessionId = resolveSessionId(input);\n if (!sessionId) return;\n\n const payload = basePayload(\"PostToolUse\", sessionId);\n payload.tool_name = input?.tool;\n payload.tool_output =\n typeof output?.output === \"string\" ? output.output : JSON.stringify(output?.output ?? null);\n\n runCommands(\"PostToolUse\", sessionId, payload, commands, true);\n },\n\n // UserPromptSubmit \u2192 chat.message. Marks the start of an active period;\n // codemie hook forwards it to SessionStore.startActivityTracking.\n \"chat.message\": async (input: any, output: any) => {\n const sessionId = resolveSessionId(input);\n if (!sessionId) return;\n\n // A new prompt opens a new active period, so the session is busy again.\n // Clearing the flag here is what lets the NEXT idle emit a Stop: on a build\n // that publishes session.idle without session.status, nothing else ever\n // resets it, so every turn after the first would be suppressed and\n // active_duration_ms would count only turn one.\n idleState.set(sessionId, false);\n\n const commands = getMatchingCommands(config, \"UserPromptSubmit\");\n if (commands.length === 0) return;\n\n // The message parts live on output, not input.\n const parts = output?.parts;\n const payload = basePayload(\"UserPromptSubmit\", sessionId);\n payload.prompt = Array.isArray(parts)\n ? parts\n .filter((p: any) => p?.type === \"text\")\n .map((p: any) => p.text)\n .join(\"\\n\")\n : \"\";\n\n runCommands(\"UserPromptSubmit\", sessionId, payload, commands, true);\n },\n\n // PreCompact \u2192 experimental.session.compacting (non-blocking)\n \"experimental.session.compacting\": async (input: any, output: any) => {\n const commands = getMatchingCommands(config, \"PreCompact\");\n if (commands.length === 0) return;\n\n const sessionId = resolveSessionId(input);\n if (!sessionId) return;\n\n const responses = runCommands(\n \"PreCompact\", sessionId, basePayload(\"PreCompact\", sessionId), commands, true\n );\n\n for (const parsed of responses) {\n if (parsed.additionalContext && output) {\n output.context = (output.context || \"\") + \"\\n\" + parsed.additionalContext;\n }\n }\n },\n\n // Stop / Notification \u2192 event bus (non-blocking).\n // SessionStart and SessionEnd are deliberately NOT mapped here: session.created\n // and session.deleted do not correspond to CLI process start/exit, and the\n // CodeMie CLI raises both lifecycle events in-process instead.\n \"event\": async (input: any) => {\n const eventType = input?.event?.type;\n if (typeof eventType !== \"string\") return;\n\n const sessionId = resolveSessionId(input);\n if (!sessionId) return;\n\n let hookEvent: HookEventName | undefined;\n\n if (eventType === \"session.idle\") {\n if (!shouldEmitIdle(sessionId, true)) return;\n hookEvent = \"Stop\";\n } else if (eventType === \"session.status\") {\n const isIdle = input?.event?.properties?.status?.type === \"idle\";\n if (!shouldEmitIdle(sessionId, isIdle)) return;\n hookEvent = \"Stop\";\n } else if (eventType === \"session.error\") {\n hookEvent = \"Notification\";\n }\n\n if (!hookEvent) return;\n\n const commands = getMatchingCommands(config, hookEvent);\n if (commands.length === 0) return;\n\n runCommands(hookEvent, sessionId, basePayload(hookEvent, sessionId), commands, true);\n },\n});\n\nexport default ShellHooksPlugin;\n";
|
|
15
27
|
//# sourceMappingURL=shell-hooks-source.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shell-hooks-source.d.ts","sourceRoot":"","sources":["../../../../src/agents/plugins/codemie-code-hooks/shell-hooks-source.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"shell-hooks-source.d.ts","sourceRoot":"","sources":["../../../../src/agents/plugins/codemie-code-hooks/shell-hooks-source.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,eAAO,MAAM,yBAAyB,guhBAkcrC,CAAC"}
|