ai-worklens-agent 0.1.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 ADDED
@@ -0,0 +1,166 @@
1
+ # Client Agent
2
+
3
+ 员工端静默采集代理,负责把 Codex/MCP/hook 等来源的事件标准化、脱敏、缓存并上传到中心端。
4
+
5
+ MVP 能力:
6
+
7
+ - 一次性安装和更新。
8
+ - 本地配置和健康检查。
9
+ - 自动会话边界。
10
+ - 本地脱敏。
11
+ - 本地上传队列。
12
+ - heartbeat。
13
+ - Codex hook adapter。
14
+ - MCP、Skill、插件、命令、验证、返工事件采集。
15
+ - 首次安装后后台静默更新,自动拉齐客户端、MCP、Hook 和插件文件版本。
16
+
17
+ 员工不需要手动输入云效任务号,也不需要手动触发开始和结束。
18
+
19
+ ## 当前命令
20
+
21
+ 在项目根目录执行:
22
+
23
+ ```bash
24
+ npm run agent:status
25
+ npm run agent:install -- --target-dir ./.worklens-client --server-url http://127.0.0.1:8797 --tool codex
26
+ npm run agent:install -- --target-dir ./.worklens-client --server-url http://127.0.0.1:8797 --tool claude-code
27
+ npm run agent:install -- --target-dir ./.worklens-client --server-url http://127.0.0.1:8797 --tool opencode
28
+ npm run agent:sync -- --config ./.worklens-client/client.json
29
+ npm run agent:auto-update -- --config ./.worklens-client/client.json
30
+ npm run agent:checkin
31
+ npm run agent:flush
32
+ npm run agent:recover
33
+ npm run mcp
34
+ ```
35
+
36
+ 如果管理员已经把员工端发布到 npm 或企业私有 npm 源,可以使用 `npx` 首次安装:
37
+
38
+ ```bash
39
+ npx -y -p ai-worklens-agent@0.1.0 worklens-agent-install \
40
+ --server-url http://127.0.0.1:8797 \
41
+ --tool codex \
42
+ --employee-pinyin zhangsan
43
+ ```
44
+
45
+ 发布到公共 npm 源需要 npm 账号 token。Scoped 包名需要账号拥有对应 scope。
46
+
47
+ ```bash
48
+ npm run client:npm:publish -- \
49
+ --package-name @your-org/ai-worklens-agent \
50
+ --version 0.2.0 \
51
+ --tag latest \
52
+ --dry-run
53
+
54
+ NPM_TOKEN=<npm_token> npm run client:npm:publish -- \
55
+ --package-name @your-org/ai-worklens-agent \
56
+ --version 0.2.0 \
57
+ --tag latest
58
+ ```
59
+
60
+ 如果管理员在官网发布了直链安装包,可以下载安装包后执行包内安装脚本:
61
+
62
+ ```bash
63
+ curl -fL http://127.0.0.1:8797/site/downloads/ai-worklens-codex-0.1.0.sh \
64
+ -o ai-worklens-install.sh
65
+ chmod +x ai-worklens-install.sh
66
+ ./ai-worklens-install.sh zhangsan
67
+ ```
68
+
69
+ 记录一个通用事件:
70
+
71
+ ```bash
72
+ npm run agent -- event \
73
+ --event-type verification \
74
+ --title "运行测试" \
75
+ --content "执行 npm test 验证通过" \
76
+ --commands "npm test" \
77
+ --duration-seconds 60
78
+ ```
79
+
80
+ 模拟 Codex hook 输入:
81
+
82
+ ```bash
83
+ echo '{"event":"plugin_use","pluginName":"Spreadsheets","message":"整理报价清单"}' | npm run hook:codex
84
+ ```
85
+
86
+ 模拟 Claude Code 工具 hook 输入:
87
+
88
+ ```bash
89
+ echo '{"hook_event_name":"PostToolUse","tool_name":"Bash","tool_input":{"command":"npm test"}}' | npm run hook:codex -- --tool claude-code
90
+ ```
91
+
92
+ 常用环境变量:
93
+
94
+ ```text
95
+ WORKLENS_SERVER_URL=http://127.0.0.1:8797
96
+ WORKLENS_EMPLOYEE_ID=E001
97
+ WORKLENS_EMPLOYEE_NAME=张三
98
+ WORKLENS_DEPARTMENT=产品研发
99
+ WORKLENS_ROLE=前端工程师
100
+ WORKLENS_CLIENT_ID=zhangsan-mac
101
+ WORKLENS_TOOL=codex
102
+ WORKLENS_MODEL_PROVIDER=openai
103
+ WORKLENS_MODEL_NAME=gpt-5.4
104
+ WORKLENS_MODEL_VERSION=5.4
105
+ WORKLENS_CONFIG_FILE=/path/to/client.json
106
+ WORKLENS_QUEUE_FILE=/path/to/queue.json
107
+ ```
108
+
109
+ ## Hook 事件映射
110
+
111
+ | Hook 输入 | 标准事件 |
112
+ | --- | --- |
113
+ | `user_prompt` / `prompt` / `user_message` | `user_prompt` |
114
+ | `assistant_response` / `assistant_turn` | `assistant_response` |
115
+ | `skill_use` | `skill_use` |
116
+ | `plugin_use` | `plugin_use` |
117
+ | `mcp_tool_call` | `mcp_tool_call` |
118
+ | `UserPromptSubmit` / `PostToolUse` / `PreToolUse` | `user_prompt` / `tool_call` / `command` |
119
+ | `command` 且命令包含 test/lint/build/typecheck | `verification` |
120
+ | `command` 普通命令 | `command` |
121
+ | `error` / `failure` | `error` |
122
+ | `rework` / `retry` / `fix` | `rework` |
123
+
124
+ 上传失败时事件会留在本地 `queue.json`,下次 `agent:flush`、`agent:recover`、后台巡检或新事件触发时继续发送。
125
+
126
+ ## 离线缓存和恢复
127
+
128
+ - 每条事件先写入本地队列,再按批上传到中心端。
129
+ - 中心端不可用、HTTP 失败、超时或只接收了部分事件时,未确认事件继续保留在 `queue.json`。
130
+ - 队列会记录失败次数、最近错误和下次重试时间,默认指数退避,避免中心端故障时持续高频请求。
131
+ - 中心端恢复后执行 `npm run agent:recover` 会同步最新采集规则、按批补传队列并上报健康状态。
132
+ - 需要人工立即补传时可执行 `npm run agent:recover -- --force`,会忽略 `nextAttemptAt` 直接重试。
133
+ - 服务端按 `eventId` 幂等接收,同一事件重传不会重复入库;客户端根据服务端返回的 `eventIds` 清理已确认事件。
134
+
135
+ ## MCP 工具
136
+
137
+ `npm run mcp` 会启动一个最小 MCP server,当前提供:
138
+
139
+ - `record_ai_event`
140
+ - `record_skill_use`
141
+ - `record_plugin_use`
142
+ - `record_mcp_tool_call`
143
+ - `record_verification`
144
+ - `client_checkin`
145
+ - `flush_queue`
146
+ - `recover_queue`
147
+ - `sync_remote_config`
148
+
149
+ 这些工具都写入同一套中心端事件协议,失败时进入本地队列。
150
+
151
+ ## 安装清单
152
+
153
+ `agent:install` 会输出员工端配置和安装清单:
154
+
155
+ - `client.json`:中心端地址、员工身份、上传配置。
156
+ - `install-manifest.json`:MCP server、Hook adapter、CLI event entrypoint。
157
+ - `codex-mcp-snippet.toml` / `codex-hook.sh`:Codex 配置片段。
158
+ - `claude-code-mcp.json` / `claude-code-hook.sh` / `claude-code-hooks-settings.json`:Claude Code 配置片段和官方 hooks events 覆盖。
159
+ - `opencode-mcp.jsonc` / `opencode-hook.sh` / `opencode-ai-worklens-plugin.js`:OpenCode MCP 配置和本地插件事件覆盖。
160
+ - `worklens-checkin.sh`:同步远程规则、补传离线队列并上报健康状态。
161
+ - `worklens-auto-update.sh`:补传离线队列,拉取中心端静默更新策略,自动重写本地采集组件。
162
+ - `worklens-register-autoupdate.sh`:在 macOS 用户级 LaunchAgent 注册后台巡检任务。
163
+ - `worklens-unregister-autoupdate.sh`:移除后台巡检任务。
164
+ - `worklens-install-or-update.sh`:同步配置、静默更新、注册后台任务并自检。
165
+
166
+ 在当前沙箱内建议指定 `--target-dir` 到项目目录;真实员工机器可以使用默认目录 `~/.ai-worklens`。
package/package.json ADDED
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "ai-worklens-agent",
3
+ "version": "0.1.0",
4
+ "description": "Employee-side collector agent for AI WorkLens.",
5
+ "type": "module",
6
+ "bin": {
7
+ "worklens-agent": "src/cli.mjs",
8
+ "worklens-agent-install": "src/install.mjs"
9
+ },
10
+ "files": [
11
+ "src",
12
+ "README.md"
13
+ ],
14
+ "keywords": [
15
+ "ai-observability",
16
+ "mcp",
17
+ "codex",
18
+ "claude-code",
19
+ "opencode"
20
+ ],
21
+ "license": "UNLICENSED"
22
+ }
package/src/cli.mjs ADDED
@@ -0,0 +1,159 @@
1
+ #!/usr/bin/env node
2
+ import { loadClientConfig, writeClientConfig, defaultConfigFile } from "./config.mjs";
3
+ import { buildEvent } from "./event-builder.mjs";
4
+ import { ClientAgent } from "./uploader.mjs";
5
+
6
+ async function readStdin() {
7
+ const chunks = [];
8
+ for await (const chunk of process.stdin) chunks.push(chunk);
9
+ const raw = Buffer.concat(chunks).toString("utf8").trim();
10
+ if (!raw) return {};
11
+ return JSON.parse(raw);
12
+ }
13
+
14
+ function parseArgs(argv) {
15
+ const result = { _: [] };
16
+ for (let index = 0; index < argv.length; index += 1) {
17
+ const arg = argv[index];
18
+ if (!arg.startsWith("--")) {
19
+ result._.push(arg);
20
+ continue;
21
+ }
22
+ const key = arg.slice(2);
23
+ const next = argv[index + 1];
24
+ if (!next || next.startsWith("--")) result[key] = true;
25
+ else {
26
+ result[key] = next;
27
+ index += 1;
28
+ }
29
+ }
30
+ return result;
31
+ }
32
+
33
+ function eventInputFromArgs(args, stdin) {
34
+ return {
35
+ ...stdin,
36
+ eventType: args["event-type"] || args.eventType || args.type || stdin.eventType,
37
+ source: args.source || stdin.source,
38
+ title: args.title || stdin.title,
39
+ content: args.content || args.summary || stdin.content || stdin.summary,
40
+ durationSeconds: args.duration || args["duration-seconds"] || stdin.durationSeconds,
41
+ files: args.files || stdin.files,
42
+ commands: args.commands || stdin.commands,
43
+ localSessionId: args.session || args["session-id"] || stdin.localSessionId,
44
+ turnIndex: args.turn || args["turn-index"] || stdin.turnIndex,
45
+ skillName: args.skill || args["skill-name"] || stdin.skillName,
46
+ pluginName: args.plugin || args["plugin-name"] || stdin.pluginName,
47
+ mcpServer: args.mcp || args["mcp-server"] || stdin.mcpServer,
48
+ hookName: args.hook || args["hook-name"] || stdin.hookName
49
+ };
50
+ }
51
+
52
+ function initConfig(args) {
53
+ const file = args.config || defaultConfigFile();
54
+ const value = {
55
+ serverUrl: args["server-url"] || "http://127.0.0.1:8797",
56
+ collectorToken: args["collector-token"] || "",
57
+ clientId: args["client-id"] || "",
58
+ clientVersion: "0.1.0",
59
+ tool: args.tool || "codex",
60
+ model: {
61
+ provider: args["model-provider"] || "",
62
+ name: args["model-name"] || "",
63
+ version: args["model-version"] || "",
64
+ family: args["model-family"] || ""
65
+ },
66
+ employee: {
67
+ id: args["employee-id"] || "",
68
+ name: args["employee-name"] || "",
69
+ pinyinName: args["employee-pinyin"] || "",
70
+ department: args.department || "",
71
+ role: args.role || ""
72
+ },
73
+ upload: {
74
+ timeoutMs: 5000,
75
+ batchSize: 50
76
+ }
77
+ };
78
+ writeClientConfig(file, value);
79
+ return { ok: true, configFile: file };
80
+ }
81
+
82
+ async function main() {
83
+ const args = parseArgs(process.argv.slice(2));
84
+ const command = args._[0] || "event";
85
+ if (command === "init") {
86
+ process.stdout.write(`${JSON.stringify(initConfig(args), null, 2)}\n`);
87
+ return;
88
+ }
89
+
90
+ const config = loadClientConfig({
91
+ configFile: args.config,
92
+ queueFile: args.queue,
93
+ serverUrl: args["server-url"],
94
+ collectorToken: args["collector-token"],
95
+ employeeId: args["employee-id"],
96
+ employeeName: args["employee-name"],
97
+ employeePinyin: args["employee-pinyin"],
98
+ department: args.department,
99
+ role: args.role,
100
+ clientId: args["client-id"],
101
+ tool: args.tool,
102
+ modelProvider: args["model-provider"],
103
+ modelName: args["model-name"],
104
+ modelVersion: args["model-version"],
105
+ modelFamily: args["model-family"],
106
+ repoName: args["repo-name"],
107
+ workspaceRoot: args.workspace,
108
+ branch: args.branch
109
+ });
110
+ const agent = new ClientAgent(config);
111
+
112
+ if (command === "status") {
113
+ process.stdout.write(`${JSON.stringify(await agent.status(), null, 2)}\n`);
114
+ return;
115
+ }
116
+ if (command === "doctor" || command === "self-check") {
117
+ process.stdout.write(`${JSON.stringify(await agent.doctor(), null, 2)}\n`);
118
+ return;
119
+ }
120
+ if (command === "flush") {
121
+ process.stdout.write(`${JSON.stringify(await agent.flush({ force: args.force === true || args.force === "true" }), null, 2)}\n`);
122
+ return;
123
+ }
124
+ if (command === "recover" || command === "retry-upload") {
125
+ process.stdout.write(`${JSON.stringify(await agent.recover({
126
+ maxBatches: args["max-batches"] || 20,
127
+ sync: args.sync !== "false",
128
+ checkin: args.checkin !== "false",
129
+ force: args.force === true || args.force === "true"
130
+ }), null, 2)}\n`);
131
+ return;
132
+ }
133
+ if (command === "checkin") {
134
+ process.stdout.write(`${JSON.stringify(await agent.checkin({
135
+ mcpReady: args["mcp-ready"] !== "false",
136
+ hookReady: args["hook-ready"] !== "false",
137
+ issues: args.issues ? String(args.issues).split(",").map((item) => item.trim()).filter(Boolean) : []
138
+ }), null, 2)}\n`);
139
+ return;
140
+ }
141
+ if (command === "sync-config") {
142
+ process.stdout.write(`${JSON.stringify(await agent.syncConfig({ write: args.write !== "false" }), null, 2)}\n`);
143
+ return;
144
+ }
145
+ if (command === "auto-update") {
146
+ process.stdout.write(`${JSON.stringify(await agent.autoUpdate({ force: args.force === true || args.force === "true" }), null, 2)}\n`);
147
+ return;
148
+ }
149
+
150
+ const stdin = await readStdin();
151
+ const event = buildEvent(eventInputFromArgs(args, stdin), config);
152
+ const result = await agent.record(event);
153
+ process.stdout.write(`${JSON.stringify({ ...result, eventId: event.eventId, eventType: event.eventType }, null, 2)}\n`);
154
+ }
155
+
156
+ main().catch((error) => {
157
+ process.stderr.write(`${JSON.stringify({ ok: false, error: error.message })}\n`);
158
+ process.exitCode = 1;
159
+ });
package/src/config.mjs ADDED
@@ -0,0 +1,83 @@
1
+ import fs from "node:fs";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import crypto from "node:crypto";
5
+ import { normalizeCollectionSettings } from "../../collector-protocol/src/collection-settings.mjs";
6
+ import { normalizeClientUpdatePolicy } from "../../collector-protocol/src/client-update-policy.mjs";
7
+ import { normalizeModelInfo } from "../../collector-protocol/src/model-info.mjs";
8
+ import { normalizeToolId } from "../../collector-protocol/src/tool-profiles.mjs";
9
+
10
+ const DEFAULT_CONFIG_DIR = ".ai-worklens";
11
+
12
+ function envValue(env, key) {
13
+ return env[key];
14
+ }
15
+
16
+ function readJson(filePath) {
17
+ try {
18
+ if (!fs.existsSync(filePath)) return {};
19
+ return JSON.parse(fs.readFileSync(filePath, "utf8"));
20
+ } catch {
21
+ return {};
22
+ }
23
+ }
24
+
25
+ function defaultClientId() {
26
+ const seed = `${os.hostname()}:${os.userInfo().username}`;
27
+ return `device_${crypto.createHash("sha256").update(seed).digest("hex").slice(0, 12)}`;
28
+ }
29
+
30
+ export function defaultConfigFile() {
31
+ return envValue(process.env, "WORKLENS_CONFIG_FILE") || path.join(os.homedir(), DEFAULT_CONFIG_DIR, "client.json");
32
+ }
33
+
34
+ export function defaultQueueFile(configFile = defaultConfigFile()) {
35
+ return envValue(process.env, "WORKLENS_QUEUE_FILE") || path.join(path.dirname(configFile), "queue.json");
36
+ }
37
+
38
+ export function loadClientConfig(options = {}) {
39
+ const configFile = options.configFile || defaultConfigFile();
40
+ const fileConfig = readJson(configFile);
41
+ const env = process.env;
42
+ const serverUrl = options.serverUrl || envValue(env, "WORKLENS_SERVER_URL") || fileConfig.serverUrl || "http://127.0.0.1:8797";
43
+ const collection = normalizeCollectionSettings(options.collection || fileConfig.collection || {});
44
+ const employee = {
45
+ id: options.employeeId || envValue(env, "WORKLENS_EMPLOYEE_ID") || fileConfig.employee?.id || os.userInfo().username,
46
+ name: options.employeeName || envValue(env, "WORKLENS_EMPLOYEE_NAME") || fileConfig.employee?.name || os.userInfo().username,
47
+ pinyinName: options.employeePinyin || options.pinyinName || envValue(env, "WORKLENS_EMPLOYEE_PINYIN") || fileConfig.employee?.pinyinName || "",
48
+ department: options.department || envValue(env, "WORKLENS_DEPARTMENT") || fileConfig.employee?.department || "",
49
+ role: options.role || envValue(env, "WORKLENS_ROLE") || fileConfig.employee?.role || ""
50
+ };
51
+ return {
52
+ configFile,
53
+ queueFile: options.queueFile || defaultQueueFile(configFile),
54
+ serverUrl,
55
+ collectorToken: options.collectorToken || envValue(env, "WORKLENS_COLLECTOR_TOKEN") || fileConfig.collectorToken || "",
56
+ clientId: options.clientId || envValue(env, "WORKLENS_CLIENT_ID") || fileConfig.clientId || defaultClientId(),
57
+ clientVersion: options.clientVersion || fileConfig.clientVersion || "0.1.0",
58
+ tool: normalizeToolId(options.tool || envValue(env, "WORKLENS_TOOL") || fileConfig.tool || "codex"),
59
+ model: normalizeModelInfo({
60
+ ...fileConfig.model,
61
+ provider: options.modelProvider || envValue(env, "WORKLENS_MODEL_PROVIDER") || fileConfig.model?.provider,
62
+ name: options.modelName || envValue(env, "WORKLENS_MODEL_NAME") || fileConfig.model?.name,
63
+ version: options.modelVersion || envValue(env, "WORKLENS_MODEL_VERSION") || fileConfig.model?.version,
64
+ family: options.modelFamily || envValue(env, "WORKLENS_MODEL_FAMILY") || fileConfig.model?.family
65
+ }),
66
+ workspaceRoot: options.workspaceRoot || envValue(env, "WORKLENS_WORKSPACE_ROOT") || process.cwd(),
67
+ repoName: options.repoName || envValue(env, "WORKLENS_REPO_NAME") || path.basename(options.workspaceRoot || envValue(env, "WORKLENS_WORKSPACE_ROOT") || process.cwd()),
68
+ branch: options.branch || envValue(env, "WORKLENS_BRANCH") || fileConfig.branch || "",
69
+ employee,
70
+ upload: {
71
+ timeoutMs: Number(options.timeoutMs || envValue(env, "WORKLENS_UPLOAD_TIMEOUT_MS") || fileConfig.upload?.timeoutMs || 5000),
72
+ batchSize: Number(options.batchSize || envValue(env, "WORKLENS_UPLOAD_BATCH_SIZE") || fileConfig.upload?.batchSize || collection.uploadBatchSize || 50)
73
+ },
74
+ collection,
75
+ update: normalizeClientUpdatePolicy(options.update || fileConfig.update || {})
76
+ };
77
+ }
78
+
79
+ export function writeClientConfig(configFile, value) {
80
+ fs.mkdirSync(path.dirname(configFile), { recursive: true, mode: 0o700 });
81
+ fs.writeFileSync(configFile, `${JSON.stringify(value, null, 2)}\n`, { mode: 0o600 });
82
+ fs.chmodSync(configFile, 0o600);
83
+ }
@@ -0,0 +1,77 @@
1
+ import path from "node:path";
2
+ import { normalizeCollectorEvent, stableHash } from "../../collector-protocol/src/event-schema.mjs";
3
+
4
+ function parseList(value) {
5
+ if (!value) return [];
6
+ if (Array.isArray(value)) return value;
7
+ return String(value).split(",").map((item) => item.trim()).filter(Boolean);
8
+ }
9
+
10
+ export function buildEvent(input = {}, config) {
11
+ const workspaceRoot = input.workspaceRoot || config.workspaceRoot || process.cwd();
12
+ const repoName = input.repoName || config.repoName || path.basename(workspaceRoot);
13
+ const metadata = input.metadata && typeof input.metadata === "object" ? input.metadata : {};
14
+ const event = normalizeCollectorEvent({
15
+ ...input,
16
+ employeeId: input.employeeId || config.employee.id,
17
+ employeeName: input.employeeName || config.employee.name,
18
+ department: input.department || config.employee.department,
19
+ role: input.role || config.employee.role,
20
+ clientId: input.clientId || config.clientId,
21
+ tool: input.tool || config.tool,
22
+ model: input.model || {
23
+ provider: input.modelProvider || config.model?.provider,
24
+ name: input.modelName || config.model?.name,
25
+ version: input.modelVersion || config.model?.version,
26
+ family: input.modelFamily || config.model?.family
27
+ },
28
+ source: input.source || "client_agent",
29
+ eventType: input.eventType || "tool_call",
30
+ workspace: {
31
+ rootHash: input.workspace?.rootHash || stableHash(workspaceRoot),
32
+ repoName: input.workspace?.repoName || repoName,
33
+ branch: input.workspace?.branch || input.branch || config.branch
34
+ },
35
+ session: {
36
+ localSessionId: input.session?.localSessionId || input.localSessionId || `${repoName}_${new Date().toISOString().slice(0, 10)}`,
37
+ turnIndex: input.session?.turnIndex || input.turnIndex || 0,
38
+ idleClosed: Boolean(input.session?.idleClosed || input.idleClosed)
39
+ },
40
+ summary: {
41
+ title: input.summary?.title || input.title || input.eventType || "AI 使用事件",
42
+ content: input.summary?.content || input.content || input.summary || input.message || ""
43
+ },
44
+ metrics: {
45
+ durationSeconds: input.metrics?.durationSeconds ?? input.durationSeconds ?? 0,
46
+ inputChars: input.metrics?.inputChars,
47
+ promptQualityScore: input.metrics?.promptQualityScore
48
+ },
49
+ process: {
50
+ ...(input.process && typeof input.process === "object" ? input.process : {}),
51
+ interactionType: input.interactionType || input.interaction_type || input.process?.interactionType,
52
+ codexMode: input.codexMode || input.codex_mode || input.mode || input.process?.codexMode,
53
+ previousMode: input.previousMode || input.previous_mode || input.fromMode || input.from_mode || input.process?.previousMode,
54
+ nextMode: input.nextMode || input.next_mode || input.toMode || input.to_mode || input.process?.nextMode,
55
+ phase: input.phase || input.process?.phase,
56
+ roundIndex: input.roundIndex || input.round_index || input.process?.roundIndex,
57
+ toolName: input.toolName || input.tool_name || input.process?.toolName,
58
+ toolStatus: input.toolStatus || input.tool_status || input.status || input.process?.toolStatus,
59
+ exitCode: input.exitCode ?? input.exit_code ?? input.process?.exitCode,
60
+ permissionDecision: input.permissionDecision || input.permission_decision || input.decision || input.process?.permissionDecision,
61
+ retryAttempt: input.retryAttempt || input.retry_attempt || input.process?.retryAttempt,
62
+ failureKind: input.failureKind || input.failure_kind || input.errorCode || input.error_code || input.process?.failureKind
63
+ },
64
+ refs: {
65
+ files: parseList(input.refs?.files || input.files),
66
+ commands: parseList(input.refs?.commands || input.commands)
67
+ },
68
+ metadata: {
69
+ ...metadata,
70
+ skillName: input.skillName || metadata.skillName,
71
+ pluginName: input.pluginName || metadata.pluginName,
72
+ mcpServer: input.mcpServer || metadata.mcpServer,
73
+ hookName: input.hookName || metadata.hookName
74
+ }
75
+ });
76
+ return event;
77
+ }