From 77c4db36d87d29407a3d8d912ce87e9b27a337e3 Mon Sep 17 00:00:00 2001 From: v6ole Date: Thu, 18 Jun 2026 15:04:15 +0800 Subject: [PATCH] v2: single plugin with 3 actions (Codex + Claude + opencode) --- .../en.json | 4 - .../manifest.json | 53 ----- .../plugin/index.js | 153 ------------- .../plugin/package-lock.json | 186 --------------- .../plugin/package.json | 13 -- .../zh_CN.json | 4 - .../.gitignore | 3 - .../en.json | 4 - .../manifest.json | 53 ----- .../plugin/autofile.js | 47 ---- .../plugin/index.js | 159 ------------- .../plugin/package.json | 13 -- .../plugin/utils/plugin.js | 213 ------------------ .../zh_CN.json | 4 - .../.gitignore | 0 com.streamdock.ai-monitor.sdPlugin/en.json | 4 + .../manifest.json | 97 ++++++++ .../plugin/autofile.js | 0 .../plugin/index.js | 195 ++++++++++++++++ .../plugin/package-lock.json | 8 +- .../plugin/package.json | 6 +- .../plugin/utils/plugin.js | 0 .../static/img/claude-icon.svg | 0 .../static/img/codex-icon.svg | 0 .../static/img/opencode-icon.svg | 0 com.streamdock.ai-monitor.sdPlugin/zh_CN.json | 4 + .../.gitignore | 3 - .../en.json | 4 - .../manifest.json | 53 ----- .../plugin/autofile.js | 47 ---- .../plugin/index.js | 145 ------------ .../plugin/package-lock.json | 186 --------------- .../plugin/utils/plugin.js | 213 ------------------ .../zh_CN.json | 4 - shared/plugin.js | 213 ------------------ 35 files changed, 307 insertions(+), 1784 deletions(-) delete mode 100644 claude/com.streamdock.ai-monitor.claude.sdPlugin/en.json delete mode 100644 claude/com.streamdock.ai-monitor.claude.sdPlugin/manifest.json delete mode 100644 claude/com.streamdock.ai-monitor.claude.sdPlugin/plugin/index.js delete mode 100644 claude/com.streamdock.ai-monitor.claude.sdPlugin/plugin/package-lock.json delete mode 100644 claude/com.streamdock.ai-monitor.claude.sdPlugin/plugin/package.json delete mode 100644 claude/com.streamdock.ai-monitor.claude.sdPlugin/zh_CN.json delete mode 100644 codex/com.streamdock.ai-monitor.codex.sdPlugin/.gitignore delete mode 100644 codex/com.streamdock.ai-monitor.codex.sdPlugin/en.json delete mode 100644 codex/com.streamdock.ai-monitor.codex.sdPlugin/manifest.json delete mode 100644 codex/com.streamdock.ai-monitor.codex.sdPlugin/plugin/autofile.js delete mode 100644 codex/com.streamdock.ai-monitor.codex.sdPlugin/plugin/index.js delete mode 100644 codex/com.streamdock.ai-monitor.codex.sdPlugin/plugin/package.json delete mode 100644 codex/com.streamdock.ai-monitor.codex.sdPlugin/plugin/utils/plugin.js delete mode 100644 codex/com.streamdock.ai-monitor.codex.sdPlugin/zh_CN.json rename {claude/com.streamdock.ai-monitor.claude.sdPlugin => com.streamdock.ai-monitor.sdPlugin}/.gitignore (100%) create mode 100644 com.streamdock.ai-monitor.sdPlugin/en.json create mode 100644 com.streamdock.ai-monitor.sdPlugin/manifest.json rename {claude/com.streamdock.ai-monitor.claude.sdPlugin => com.streamdock.ai-monitor.sdPlugin}/plugin/autofile.js (100%) create mode 100644 com.streamdock.ai-monitor.sdPlugin/plugin/index.js rename {codex/com.streamdock.ai-monitor.codex.sdPlugin => com.streamdock.ai-monitor.sdPlugin}/plugin/package-lock.json (98%) rename {opencode/com.streamdock.ai-monitor.opencode.sdPlugin => com.streamdock.ai-monitor.sdPlugin}/plugin/package.json (59%) rename {claude/com.streamdock.ai-monitor.claude.sdPlugin => com.streamdock.ai-monitor.sdPlugin}/plugin/utils/plugin.js (100%) rename {claude/com.streamdock.ai-monitor.claude.sdPlugin => com.streamdock.ai-monitor.sdPlugin}/static/img/claude-icon.svg (100%) rename {codex/com.streamdock.ai-monitor.codex.sdPlugin => com.streamdock.ai-monitor.sdPlugin}/static/img/codex-icon.svg (100%) rename {opencode/com.streamdock.ai-monitor.opencode.sdPlugin => com.streamdock.ai-monitor.sdPlugin}/static/img/opencode-icon.svg (100%) create mode 100644 com.streamdock.ai-monitor.sdPlugin/zh_CN.json delete mode 100644 opencode/com.streamdock.ai-monitor.opencode.sdPlugin/.gitignore delete mode 100644 opencode/com.streamdock.ai-monitor.opencode.sdPlugin/en.json delete mode 100644 opencode/com.streamdock.ai-monitor.opencode.sdPlugin/manifest.json delete mode 100644 opencode/com.streamdock.ai-monitor.opencode.sdPlugin/plugin/autofile.js delete mode 100644 opencode/com.streamdock.ai-monitor.opencode.sdPlugin/plugin/index.js delete mode 100644 opencode/com.streamdock.ai-monitor.opencode.sdPlugin/plugin/package-lock.json delete mode 100644 opencode/com.streamdock.ai-monitor.opencode.sdPlugin/plugin/utils/plugin.js delete mode 100644 opencode/com.streamdock.ai-monitor.opencode.sdPlugin/zh_CN.json delete mode 100644 shared/plugin.js diff --git a/claude/com.streamdock.ai-monitor.claude.sdPlugin/en.json b/claude/com.streamdock.ai-monitor.claude.sdPlugin/en.json deleted file mode 100644 index 57d72b0..0000000 --- a/claude/com.streamdock.ai-monitor.claude.sdPlugin/en.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "Name": "Claude Desktop Monitor", - "Description": "Monitor Claude Desktop AI workflow status" -} \ No newline at end of file diff --git a/claude/com.streamdock.ai-monitor.claude.sdPlugin/manifest.json b/claude/com.streamdock.ai-monitor.claude.sdPlugin/manifest.json deleted file mode 100644 index 793c7de..0000000 --- a/claude/com.streamdock.ai-monitor.claude.sdPlugin/manifest.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "Actions": [ - { - "Icon": "static/img/claude-icon.svg", - "Name": "Claude Desktop", - "States": [ - { - "Image": "static/img/claude-icon.svg", - "TitleAlignment": "center", - "FontSize": "11" - } - ], - "Settings": { - "pollInterval": 1000 - }, - "Controllers": [ - "Keypad", - "Information" - ], - "UserTitleEnabled": true, - "SupportedInMultiActions": true, - "Tooltip": "Claude Desktop 工作流状态监控", - "UUID": "com.streamdock.ai-monitor.claude" - } - ], - "SDKVersion": 1, - "Author": "StreamDock", - "Name": "AI Monitor - Claude Desktop", - "Icon": "static/img/claude-icon.svg", - "Category": "AI Monitor", - "CategoryIcon": "static/img/claude-icon.svg", - "CodePathWin": "plugin/index.js", - "CodePathMac": "plugin/index.js", - "Description": "监控 Claude Desktop AI 工作流状态,在 N4 按钮上实时显示", - "Version": "1.0.0", - "URL": "http://10.10.10.14:18003/v6ole/streamdock-ai-monitor", - "OS": [ - { - "Platform": "windows", - "MinimumVersion": "7" - }, - { - "Platform": "mac", - "MinimumVersion": "10.11" - } - ], - "Software": { - "MinimumVersion": "3.10.188.226" - }, - "Nodejs": { - "Version": "20" - } -} \ No newline at end of file diff --git a/claude/com.streamdock.ai-monitor.claude.sdPlugin/plugin/index.js b/claude/com.streamdock.ai-monitor.claude.sdPlugin/plugin/index.js deleted file mode 100644 index 6f044b0..0000000 --- a/claude/com.streamdock.ai-monitor.claude.sdPlugin/plugin/index.js +++ /dev/null @@ -1,153 +0,0 @@ -const { Plugins, Actions, log } = require('./utils/plugin'); -const fs = require('fs'); -const path = require('path'); -const os = require('os'); - -const plugin = new Plugins('ai-monitor-claude'); -const timers = {}; -const sessionOffsets = {}; - -const HOME = os.homedir(); -const SCAN_DIR = path.join(HOME, '.claude', 'projects'); -const POLL_INTERVAL = 1000; -const IDLE_TIMEOUT_MS = 3000; - -// Load logo -var logoUri = ''; -try { - var logoPath = path.join(__dirname, '..', 'static', 'img', 'claude-icon.svg'); - logoUri = 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(fs.readFileSync(logoPath, 'utf-8')); -} catch (_) {} - -var STATUS = { - idle: { title: 'Ready', fill: '#22c55e', blink: false, breathe: false }, - prompt: { title: 'Prompt', fill: '#3b82f6', blink: false, breathe: false }, - thinking: { title: 'Thinking', fill: '#f59e0b', blink: false, breathe: true }, - working: { title: 'Working', fill: '#f59e0b', blink: true, breathe: false }, - typing: { title: 'Typing', fill: '#f59e0b', blink: false, breathe: true }, - done: { title: 'Done', fill: '#22c55e', blink: true, breathe: false }, - error: { title: 'Error', fill: '#ef4444', blink: false, breathe: false }, -}; - -var PRIORITY = ['error', 'working', 'typing', 'thinking', 'prompt', 'done', 'idle']; -var currentCtx = null, currentState = 'idle', lastActivityTime = 0; -var animationHandle = null; - -function makeSvg(fill, label, blink, breathe) { - var anim = ''; - if (blink) anim = ''; - else if (breathe) anim = ''; - return '' + anim + '' + label + ''; -} - -function setDisplay(ctx, state) { - var d = STATUS[state] || STATUS['idle']; - plugin.setTitle(ctx, d.title); - if (state === 'idle' && logoUri) plugin.setImage(ctx, logoUri); - else plugin.setImage(ctx, 'data:image/svg+xml;charset=utf-8,' + makeSvg(d.fill, d.title, d.blink, d.breathe)); -} - -function animateTick() { - if (!currentCtx) return; - var d = STATUS[currentState] || STATUS['idle']; - if (d.blink || d.breathe) { - var now = Date.now(), period = d.blink ? 800 : 2000, phase = (now % period) / period; - var alpha = d.blink ? (phase < 0.5 ? 1 : 0.2) : 0.4 + 0.6 * Math.sin(phase * Math.PI); - var r = parseInt(d.fill.slice(1,3),16), g = parseInt(d.fill.slice(3,5),16), b = parseInt(d.fill.slice(5,7),16); - var color = 'rgb(' + Math.round(16+(r-16)*alpha) + ',' + Math.round(16+(g-16)*alpha) + ',' + Math.round(16+(b-16)*alpha) + ')'; - var svg = '' + d.title + ''; - plugin.setImage(currentCtx, 'data:image/svg+xml;charset=utf-8,' + svg); - } -} - -function setState(ctx, state) { - if (state === currentState) return; - if (state === 'done') { setDisplay(ctx, 'done'); currentState = 'done'; setTimeout(function() { if (currentState === 'done') { currentState = 'idle'; setDisplay(ctx, 'idle'); } }, 800); } - else { currentState = state; setDisplay(ctx, state); } -} - -function normalize(obj) { - var t = obj.type || ''; - if (t === 'user') return 'prompt'; - if (t === 'assistant') { - var content = (obj.message || {}).content || []; - for (var i = 0; i < content.length; i++) { - var ct = content[i].type || ''; - if (ct === 'tool_use') return 'working'; - if (ct === 'thinking') return 'thinking'; - } - for (var j = 0; j < content.length; j++) { - if ((content[j].type || '') === 'text') return 'typing'; - } - return 'typing'; - } - if (t === 'system') { if (obj.subtype === 'turn_duration') return 'done'; return null; } - return null; -} - -function findFiles(rootDir, suffix) { - var results = []; - if (!fs.existsSync(rootDir)) return results; - (function walk(dir, depth) { - if (depth > 16) return; - var names; try { names = fs.readdirSync(dir); } catch (_) { return; } - for (var i = 0; i < names.length; i++) { - var full = path.join(dir, names[i]); - var st; try { st = fs.statSync(full); } catch (_) { continue; } - if (st.isDirectory()) walk(full, depth + 1); - else if (st.isFile() && names[i].endsWith(suffix)) results.push(full); - } - })(rootDir, 0); - return results; -} - -function tailFile(filePath, ctx) { - var detected = false; - try { - var stat = fs.statSync(filePath); - var off = sessionOffsets[filePath] || 0; - if (stat.size < off) { sessionOffsets[filePath] = 0; return false; } - if (stat.size === off) return false; - var fd = fs.openSync(filePath, 'r'); - var grow = Math.min(stat.size - off, 65536); - var buf = Buffer.alloc(grow); - fs.readSync(fd, buf, 0, grow, off); - fs.closeSync(fd); - sessionOffsets[filePath] = stat.size; - var lines = buf.toString('utf-8').split('\n').filter(function(l) { return l.trim(); }); - var bestPri = PRIORITY.length, bestState = null; - for (var i = 0; i < lines.length; i++) { - try { var obj = JSON.parse(lines[i]); var s = normalize(obj); if (s) { var pri = PRIORITY.indexOf(s); if (pri >= 0 && pri < bestPri) { bestPri = pri; bestState = s; } } } catch (_) {} - } - if (bestState) { setState(ctx, bestState); detected = true; } - } catch (_) { delete sessionOffsets[filePath]; } - return detected; -} - -function poll(ctx) { - var detected = false; - var files = findFiles(SCAN_DIR, '.jsonl'); - files.sort(function(a,b) { return fs.statSync(b).mtimeMs - fs.statSync(a).mtimeMs; }); - for (var i = 0; i < Math.min(files.length, 3); i++) { if (tailFile(files[i], ctx)) detected = true; } - if (detected) lastActivityTime = Date.now(); - if (currentState !== 'idle' && currentState !== 'done' && Date.now() - lastActivityTime > IDLE_TIMEOUT_MS) { setState(ctx, 'idle'); } -} - -plugin.claude = new Actions({ - default: { pollInterval: POLL_INTERVAL }, - _willAppear: function(data) { - var ctx = data.context; - currentCtx = ctx; currentState = 'idle'; lastActivityTime = Date.now(); - setDisplay(ctx, 'idle'); - poll(ctx); - timers[ctx] = setInterval(function() { poll(ctx); }, POLL_INTERVAL); - if (!animationHandle) animationHandle = setInterval(animateTick, 200); - }, - _willDisappear: function(data) { - var ctx = data.context; - if (timers[ctx]) { clearInterval(timers[ctx]); delete timers[ctx]; } - if (currentCtx === ctx) { currentCtx = null; currentState = 'idle'; } - if (Object.keys(timers).length === 0 && animationHandle) { clearInterval(animationHandle); animationHandle = null; } - }, - keyUp: function(data) { poll(data.context); } -}); \ No newline at end of file diff --git a/claude/com.streamdock.ai-monitor.claude.sdPlugin/plugin/package-lock.json b/claude/com.streamdock.ai-monitor.claude.sdPlugin/plugin/package-lock.json deleted file mode 100644 index a1679d1..0000000 --- a/claude/com.streamdock.ai-monitor.claude.sdPlugin/plugin/package-lock.json +++ /dev/null @@ -1,186 +0,0 @@ -{ - "name": "ai-monitor-claude", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "ai-monitor-claude", - "version": "1.0.0", - "license": "MIT", - "dependencies": { - "fs-extra": "^11.3.0", - "log4js": "^6.9.1", - "ws": "^8.14.2" - } - }, - "node_modules/date-format": { - "version": "4.0.14", - "resolved": "https://registry.npmjs.org/date-format/-/date-format-4.0.14.tgz", - "integrity": "sha512-39BOQLs9ZjKh0/patS9nrT8wc3ioX3/eA/zgbKNopnF2wCqJEoxywwwElATYvRsXdnOxA/OQeQoFZ3rFjVajhg==", - "license": "MIT", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/flatted": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", - "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", - "license": "ISC" - }, - "node_modules/fs-extra": { - "version": "11.3.5", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.5.tgz", - "integrity": "sha512-eKpRKAovdpZtR1WopLHxlBWvAgPny3c4gX1G5Jhwmmw4XJj0ifSD5qB5TOo8hmA0wlRKDAOAhEE1yVPgs6Fgcg==", - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=14.14" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "license": "ISC" - }, - "node_modules/jsonfile": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", - "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", - "license": "MIT", - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/log4js": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/log4js/-/log4js-6.9.1.tgz", - "integrity": "sha512-1somDdy9sChrr9/f4UlzhdaGfDR2c/SaD2a4T7qEkG4jTS57/B3qmnjLYePwQ8cqWnUHZI0iAKxMBpCZICiZ2g==", - "license": "Apache-2.0", - "dependencies": { - "date-format": "^4.0.14", - "debug": "^4.3.4", - "flatted": "^3.2.7", - "rfdc": "^1.3.0", - "streamroller": "^3.1.5" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, - "node_modules/rfdc": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", - "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", - "license": "MIT" - }, - "node_modules/streamroller": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-3.1.5.tgz", - "integrity": "sha512-KFxaM7XT+irxvdqSP1LGLgNWbYN7ay5owZ3r/8t77p+EtSUAfUgtl7be3xtqtOmGUl9K9YPO2ca8133RlTjvKw==", - "license": "MIT", - "dependencies": { - "date-format": "^4.0.14", - "debug": "^4.3.4", - "fs-extra": "^8.1.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/streamroller/node_modules/fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - }, - "engines": { - "node": ">=6 <7 || >=8" - } - }, - "node_modules/streamroller/node_modules/jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", - "license": "MIT", - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/streamroller/node_modules/universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", - "license": "MIT", - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "license": "MIT", - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/ws": { - "version": "8.21.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", - "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", - "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - } - } -} diff --git a/claude/com.streamdock.ai-monitor.claude.sdPlugin/plugin/package.json b/claude/com.streamdock.ai-monitor.claude.sdPlugin/plugin/package.json deleted file mode 100644 index 122c141..0000000 --- a/claude/com.streamdock.ai-monitor.claude.sdPlugin/plugin/package.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "name": "ai-monitor-claude", - "version": "1.0.0", - "author": "StreamDock", - "main": "index.js", - "description": "Claude Desktop AI workflow status monitor", - "license": "MIT", - "dependencies": { - "fs-extra": "^11.3.0", - "log4js": "^6.9.1", - "ws": "^8.14.2" - } -} \ No newline at end of file diff --git a/claude/com.streamdock.ai-monitor.claude.sdPlugin/zh_CN.json b/claude/com.streamdock.ai-monitor.claude.sdPlugin/zh_CN.json deleted file mode 100644 index f63422c..0000000 --- a/claude/com.streamdock.ai-monitor.claude.sdPlugin/zh_CN.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "Name": "Claude Desktop 监控", - "Description": "监控 Claude Desktop AI 工作流状态" -} \ No newline at end of file diff --git a/codex/com.streamdock.ai-monitor.codex.sdPlugin/.gitignore b/codex/com.streamdock.ai-monitor.codex.sdPlugin/.gitignore deleted file mode 100644 index ca5cc30..0000000 --- a/codex/com.streamdock.ai-monitor.codex.sdPlugin/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -node_modules/ -*.log -build/ \ No newline at end of file diff --git a/codex/com.streamdock.ai-monitor.codex.sdPlugin/en.json b/codex/com.streamdock.ai-monitor.codex.sdPlugin/en.json deleted file mode 100644 index 6d220c6..0000000 --- a/codex/com.streamdock.ai-monitor.codex.sdPlugin/en.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "Name": "Codex Desktop Monitor", - "Description": "Monitor Codex Desktop AI workflow status" -} \ No newline at end of file diff --git a/codex/com.streamdock.ai-monitor.codex.sdPlugin/manifest.json b/codex/com.streamdock.ai-monitor.codex.sdPlugin/manifest.json deleted file mode 100644 index 79526e9..0000000 --- a/codex/com.streamdock.ai-monitor.codex.sdPlugin/manifest.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "Actions": [ - { - "Icon": "static/img/codex-icon.svg", - "Name": "Codex Desktop", - "States": [ - { - "Image": "static/img/codex-icon.svg", - "TitleAlignment": "center", - "FontSize": "11" - } - ], - "Settings": { - "pollInterval": 1000 - }, - "Controllers": [ - "Keypad", - "Information" - ], - "UserTitleEnabled": true, - "SupportedInMultiActions": true, - "Tooltip": "Codex Desktop 工作流状态监控", - "UUID": "com.streamdock.ai-monitor.codex" - } - ], - "SDKVersion": 1, - "Author": "StreamDock", - "Name": "AI Monitor - Codex Desktop", - "Icon": "static/img/codex-icon.svg", - "Category": "AI Monitor", - "CategoryIcon": "static/img/codex-icon.svg", - "CodePathWin": "plugin/index.js", - "CodePathMac": "plugin/index.js", - "Description": "监控 Codex Desktop AI 工作流状态,在 N4 按钮上实时显示", - "Version": "1.0.0", - "URL": "http://10.10.10.14:18003/v6ole/streamdock-ai-monitor", - "OS": [ - { - "Platform": "windows", - "MinimumVersion": "7" - }, - { - "Platform": "mac", - "MinimumVersion": "10.11" - } - ], - "Software": { - "MinimumVersion": "3.10.188.226" - }, - "Nodejs": { - "Version": "20" - } -} \ No newline at end of file diff --git a/codex/com.streamdock.ai-monitor.codex.sdPlugin/plugin/autofile.js b/codex/com.streamdock.ai-monitor.codex.sdPlugin/plugin/autofile.js deleted file mode 100644 index 38c45b6..0000000 --- a/codex/com.streamdock.ai-monitor.codex.sdPlugin/plugin/autofile.js +++ /dev/null @@ -1,47 +0,0 @@ -const path = require('path'); -const fs = require('fs-extra'); - -console.log('开始执行自动化构建...'); - -const currentDir = __dirname; - -// 获取父文件夹的路径 -const parentDir = path.join(currentDir, '..'); -// 获取父文件夹的名称 -const PluginName = path.basename(parentDir); - - -const PluginPath = path.join(process.env.APPDATA, 'HotSpot/StreamDock/plugins', PluginName); - -try { - // 删除旧的插件目录 - fs.removeSync(PluginPath); - - // 确保目标目录存在 - fs.ensureDirSync(path.dirname(PluginPath)); - - // 复制当前目录到目标路径,排除 node_modules - fs.copySync(path.resolve(__dirname, '..'), PluginPath, { - filter: (src) => { - const relativePath = path.relative(path.resolve(__dirname, '..'), src); - // 排除 'node_modules' 和 '.git' 目录及其子文件 - return !relativePath.startsWith('plugin\\node_modules') - &&!relativePath.startsWith('plugin\\index.js') - &&!relativePath.startsWith('plugin\\package.json') - &&!relativePath.startsWith('plugin\\package-lock.json') - &&!relativePath.startsWith('plugin\\pnpm-lock.yaml') - &&!relativePath.startsWith('plugin\\yarn.lock') - &&!relativePath.startsWith('plugin\\build') - &&!relativePath.startsWith('plugin\\log') - &&!relativePath.startsWith('.git') - &&!relativePath.startsWith('.vscode'); - } - }); - - fs.copySync( path.join(__dirname, "build"), path.join(PluginPath,'plugin')) - - console.log(`插件 "${PluginName}" 已成功复制到 "${PluginPath}"`); - console.log('构建成功-------------'); -} catch (err) { - console.error(`复制出错 "${PluginName}":`, err); -} \ No newline at end of file diff --git a/codex/com.streamdock.ai-monitor.codex.sdPlugin/plugin/index.js b/codex/com.streamdock.ai-monitor.codex.sdPlugin/plugin/index.js deleted file mode 100644 index 8b168cf..0000000 --- a/codex/com.streamdock.ai-monitor.codex.sdPlugin/plugin/index.js +++ /dev/null @@ -1,159 +0,0 @@ -const { Plugins, Actions, log } = require('./utils/plugin'); -const fs = require('fs'); -const path = require('path'); -const os = require('os'); - -const plugin = new Plugins('ai-monitor-codex'); -const timers = {}; -const sessionOffsets = {}; - -const HOME = os.homedir(); -const SCAN_DIR = path.join(HOME, '.codex', 'sessions'); -const POLL_INTERVAL = 1000; -const IDLE_TIMEOUT_MS = 3000; - -// Load logo -var logoUri = ''; -try { - var logoPath = path.join(__dirname, '..', 'static', 'img', 'codex-icon.svg'); - logoUri = 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(fs.readFileSync(logoPath, 'utf-8')); -} catch (_) {} - -var STATUS = { - idle: { title: 'Ready', fill: '#22c55e', blink: false, breathe: false }, - prompt: { title: 'Prompt', fill: '#3b82f6', blink: false, breathe: false }, - thinking: { title: 'Thinking', fill: '#f59e0b', blink: false, breathe: true }, - working: { title: 'Working', fill: '#f59e0b', blink: true, breathe: false }, - typing: { title: 'Typing', fill: '#f59e0b', blink: false, breathe: true }, - done: { title: 'Done', fill: '#22c55e', blink: true, breathe: false }, - error: { title: 'Error', fill: '#ef4444', blink: false, breathe: false }, -}; - -var PRIORITY = ['error', 'working', 'typing', 'thinking', 'prompt', 'done', 'idle']; -var currentCtx = null, currentState = 'idle', lastActivityTime = 0; -var animationHandle = null; - -function makeSvg(fill, label, blink, breathe) { - var anim = ''; - if (blink) anim = ''; - else if (breathe) anim = ''; - return '' + anim + '' + label + ''; -} - -function setDisplay(ctx, state) { - var d = STATUS[state] || STATUS['idle']; - plugin.setTitle(ctx, d.title); - if (state === 'idle' && logoUri) plugin.setImage(ctx, logoUri); - else plugin.setImage(ctx, 'data:image/svg+xml;charset=utf-8,' + makeSvg(d.fill, d.title, d.blink, d.breathe)); -} - -function animateTick() { - if (!currentCtx) return; - var d = STATUS[currentState] || STATUS['idle']; - if (d.blink || d.breathe) { - var now = Date.now(), period = d.blink ? 800 : 2000, phase = (now % period) / period; - var alpha = d.blink ? (phase < 0.5 ? 1 : 0.2) : 0.4 + 0.6 * Math.sin(phase * Math.PI); - var r = parseInt(d.fill.slice(1,3),16), g = parseInt(d.fill.slice(3,5),16), b = parseInt(d.fill.slice(5,7),16); - var color = 'rgb(' + Math.round(16+(r-16)*alpha) + ',' + Math.round(16+(g-16)*alpha) + ',' + Math.round(16+(b-16)*alpha) + ')'; - var svg = '' + d.title + ''; - plugin.setImage(currentCtx, 'data:image/svg+xml;charset=utf-8,' + svg); - } -} - -function setState(ctx, state) { - if (state === currentState) return; - if (state === 'done') { setDisplay(ctx, 'done'); currentState = 'done'; setTimeout(function() { if (currentState === 'done') { currentState = 'idle'; setDisplay(ctx, 'idle'); } }, 800); } - else { currentState = state; setDisplay(ctx, state); } -} - -function normalize(obj) { - var t = obj.type || ''; - var pt = (obj.payload || {}).type || ''; - var it = ((obj.payload || {}).item || {}).type || ''; - if (t === 'event_msg') { - if (pt === 'task_started') return 'thinking'; - if (pt === 'task_complete') return 'done'; - if (pt === 'turn_aborted') return 'error'; - if (pt === 'agent_message') return 'typing'; - if (pt === 'user_message') return 'prompt'; - if (pt === 'web_search_end') return 'working'; - if (pt === 'agent_reasoning') return 'thinking'; - return null; - } - if (t === 'response_item') { - var nt = pt || it; - if (nt === 'function_call' || nt === 'custom_tool_call' || nt === 'function_call_output' || nt === 'custom_tool_call_output') return 'working'; - if (nt === 'web_search_call') return 'working'; - if (nt === 'reasoning') return 'thinking'; - if (nt === 'message') return 'typing'; - return null; - } - return null; -} - -function findFiles(rootDir, suffix) { - var results = []; - if (!fs.existsSync(rootDir)) return results; - (function walk(dir, depth) { - if (depth > 16) return; - var names; try { names = fs.readdirSync(dir); } catch (_) { return; } - for (var i = 0; i < names.length; i++) { - var full = path.join(dir, names[i]); - var st; try { st = fs.statSync(full); } catch (_) { continue; } - if (st.isDirectory()) walk(full, depth + 1); - else if (st.isFile() && names[i].endsWith(suffix)) results.push(full); - } - })(rootDir, 0); - return results; -} - -function tailFile(filePath, ctx) { - var detected = false; - try { - var stat = fs.statSync(filePath); - var off = sessionOffsets[filePath] || 0; - if (stat.size < off) { sessionOffsets[filePath] = 0; return false; } - if (stat.size === off) return false; - var fd = fs.openSync(filePath, 'r'); - var grow = Math.min(stat.size - off, 65536); - var buf = Buffer.alloc(grow); - fs.readSync(fd, buf, 0, grow, off); - fs.closeSync(fd); - sessionOffsets[filePath] = stat.size; - var lines = buf.toString('utf-8').split('\n').filter(function(l) { return l.trim(); }); - var bestPri = PRIORITY.length, bestState = null; - for (var i = 0; i < lines.length; i++) { - try { var obj = JSON.parse(lines[i]); var s = normalize(obj); if (s) { var pri = PRIORITY.indexOf(s); if (pri >= 0 && pri < bestPri) { bestPri = pri; bestState = s; } } } catch (_) {} - } - if (bestState) { setState(ctx, bestState); detected = true; } - } catch (_) { delete sessionOffsets[filePath]; } - return detected; -} - -function poll(ctx) { - var detected = false; - var files = findFiles(SCAN_DIR, '.jsonl'); - files.sort(function(a,b) { return fs.statSync(b).mtimeMs - fs.statSync(a).mtimeMs; }); - for (var i = 0; i < Math.min(files.length, 3); i++) { if (tailFile(files[i], ctx)) detected = true; } - if (detected) lastActivityTime = Date.now(); - if (currentState !== 'idle' && currentState !== 'done' && Date.now() - lastActivityTime > IDLE_TIMEOUT_MS) { setState(ctx, 'idle'); } -} - -plugin.codex = new Actions({ - default: { pollInterval: POLL_INTERVAL }, - _willAppear: function(data) { - var ctx = data.context; - currentCtx = ctx; currentState = 'idle'; lastActivityTime = Date.now(); - setDisplay(ctx, 'idle'); - poll(ctx); - timers[ctx] = setInterval(function() { poll(ctx); }, POLL_INTERVAL); - if (!animationHandle) animationHandle = setInterval(animateTick, 200); - }, - _willDisappear: function(data) { - var ctx = data.context; - if (timers[ctx]) { clearInterval(timers[ctx]); delete timers[ctx]; } - if (currentCtx === ctx) { currentCtx = null; currentState = 'idle'; } - if (Object.keys(timers).length === 0 && animationHandle) { clearInterval(animationHandle); animationHandle = null; } - }, - keyUp: function(data) { poll(data.context); } -}); \ No newline at end of file diff --git a/codex/com.streamdock.ai-monitor.codex.sdPlugin/plugin/package.json b/codex/com.streamdock.ai-monitor.codex.sdPlugin/plugin/package.json deleted file mode 100644 index 0853b88..0000000 --- a/codex/com.streamdock.ai-monitor.codex.sdPlugin/plugin/package.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "name": "ai-monitor-codex", - "version": "1.0.0", - "author": "StreamDock", - "main": "index.js", - "description": "Codex Desktop AI workflow status monitor", - "license": "MIT", - "dependencies": { - "fs-extra": "^11.3.0", - "log4js": "^6.9.1", - "ws": "^8.14.2" - } -} \ No newline at end of file diff --git a/codex/com.streamdock.ai-monitor.codex.sdPlugin/plugin/utils/plugin.js b/codex/com.streamdock.ai-monitor.codex.sdPlugin/plugin/utils/plugin.js deleted file mode 100644 index 673de5c..0000000 --- a/codex/com.streamdock.ai-monitor.codex.sdPlugin/plugin/utils/plugin.js +++ /dev/null @@ -1,213 +0,0 @@ -// 配置日志文件 -const now = new Date(); -const log = require('log4js').configure({ - appenders: { - file: { type: 'file', filename: `./log/${now.getFullYear()}.${now.getMonth() + 1}.${now.getDate()}.log` } - }, - categories: { - default: { appenders: ['file'], level: 'info' } - } -}).getLogger(); - -//################################################## -//##################全局异常捕获##################### -process.on('uncaughtException', (error) => { - log.error('Uncaught Exception:', error); -}); -process.on('unhandledRejection', (reason) => { - log.error('Unhandled Rejection:', reason); -}); -//################################################## -//################################################## - - -// 插件类 -const ws = require('ws'); -class Plugins { - static language = JSON.parse(process.argv[9]).application.language; - static globalSettings = {}; - getGlobalSettingsFlag = true; - constructor() { - if (Plugins.instance) { - return Plugins.instance; - } - // log.info("process.argv", process.argv); - this.ws = new ws("ws://127.0.0.1:" + process.argv[3]); - this.ws.on('open', () => this.ws.send(JSON.stringify({ uuid: process.argv[5], event: process.argv[7] }))); - this.ws.on('close', process.exit); - this.ws.on('message', e => { - if (this.getGlobalSettingsFlag) { - // 只获取一次 - this.getGlobalSettingsFlag = false; - this.getGlobalSettings(); - } - const data = JSON.parse(e.toString()); - const action = data.action?.split('.').pop(); - this[action]?.[data.event]?.(data); - if (data.event === 'didReceiveGlobalSettings') { - Plugins.globalSettings = data.payload.settings; - } - this[data.event]?.(data); - }); - Plugins.instance = this; - } - - setGlobalSettings(payload) { - Plugins.globalSettings = payload; - this.ws.send(JSON.stringify({ - event: "setGlobalSettings", - context: process.argv[5], payload - })); - } - - getGlobalSettings() { - this.ws.send(JSON.stringify({ - event: "getGlobalSettings", - context: process.argv[5], - })); - } - // 设置标题 - setTitle(context, str, row = 0, num = 6) { - let newStr = null; - if (row && str) { - let nowRow = 1, strArr = str.split(''); - strArr.forEach((item, index) => { - if (nowRow < row && index >= nowRow * num) { nowRow++; newStr += '\n'; } - if (nowRow <= row && index < nowRow * num) { newStr += item; } - }); - if (strArr.length > row * num) { newStr = newStr.substring(0, newStr.length - 1); newStr += '..'; } - } - this.ws.send(JSON.stringify({ - event: "setTitle", - context, payload: { - target: 0, - title: newStr || str + '' - } - })); - } - // 设置背景 - setImage(context, url) { - this.ws.send(JSON.stringify({ - event: "setImage", - context, payload: { - target: 0, - image: url - } - })); - } - // 设置状态 - setState(context, state) { - this.ws.send(JSON.stringify({ - event: "setState", - context, payload: { state } - })); - } - // 保存持久化数据 - setSettings(context, payload) { - this.ws.send(JSON.stringify({ - event: "setSettings", - context, payload - })); - } - - // 在按键上展示警告 - showAlert(context) { - this.ws.send(JSON.stringify({ - event: "showAlert", - context - })); - } - - // 在按键上展示成功 - showOk(context) { - this.ws.send(JSON.stringify({ - event: "showOk", - context - })); - } - // 发送给属性检测器 - sendToPropertyInspector(payload) { - this.ws.send(JSON.stringify({ - action: Actions.currentAction, - context: Actions.currentContext, - payload, event: "sendToPropertyInspector" - })); - } - // 用默认浏览器打开网页 - openUrl(url) { - this.ws.send(JSON.stringify({ - event: "openUrl", - payload: { url } - })); - } -}; - -// 操作类 -class Actions { - constructor(data) { - this.data = {}; - this.default = {}; - Object.assign(this, data); - } - // 属性检查器显示时 - static currentAction = null; - static currentContext = null; - static actions = {}; - propertyInspectorDidAppear(data) { - Actions.currentAction = data.action; - Actions.currentContext = data.context; - this._propertyInspectorDidAppear?.(data); - } - // 初始化数据 - willAppear(data) { - Plugins.globalContext = data.context; - Actions.actions[data.context] = data.action - const { context, payload: { settings } } = data; - this.data[context] = Object.assign({ ...this.default }, settings); - this._willAppear?.(data); - } - - didReceiveSettings(data) { - this.data[data.context] = data.payload.settings; - this._didReceiveSettings?.(data); - } - // 行动销毁 - willDisappear(data) { - this._willDisappear?.(data); - delete this.data[data.context]; - } -} - -class EventEmitter { - constructor() { - this.events = {}; - } - - // 订阅事件 - subscribe(event, listener) { - if (!this.events[event]) { - this.events[event] = []; - } - this.events[event].push(listener); - } - - // 取消订阅 - unsubscribe(event, listenerToRemove) { - if (!this.events[event]) return; - - this.events[event] = this.events[event].filter(listener => listener !== listenerToRemove); - } - - // 发布事件 - emit(event, data) { - if (!this.events[event]) return; - this.events[event].forEach(listener => listener(data)); - } -} - -module.exports = { - log, - Plugins, - Actions, - EventEmitter -}; \ No newline at end of file diff --git a/codex/com.streamdock.ai-monitor.codex.sdPlugin/zh_CN.json b/codex/com.streamdock.ai-monitor.codex.sdPlugin/zh_CN.json deleted file mode 100644 index 298279d..0000000 --- a/codex/com.streamdock.ai-monitor.codex.sdPlugin/zh_CN.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "Name": "Codex Desktop 监控", - "Description": "监控 Codex Desktop AI 工作流状态" -} \ No newline at end of file diff --git a/claude/com.streamdock.ai-monitor.claude.sdPlugin/.gitignore b/com.streamdock.ai-monitor.sdPlugin/.gitignore similarity index 100% rename from claude/com.streamdock.ai-monitor.claude.sdPlugin/.gitignore rename to com.streamdock.ai-monitor.sdPlugin/.gitignore diff --git a/com.streamdock.ai-monitor.sdPlugin/en.json b/com.streamdock.ai-monitor.sdPlugin/en.json new file mode 100644 index 0000000..98604dc --- /dev/null +++ b/com.streamdock.ai-monitor.sdPlugin/en.json @@ -0,0 +1,4 @@ +{ + "Name": "AI Monitor", + "Description": "Real-time AI agent workflow status on N4" +} \ No newline at end of file diff --git a/com.streamdock.ai-monitor.sdPlugin/manifest.json b/com.streamdock.ai-monitor.sdPlugin/manifest.json new file mode 100644 index 0000000..fe3d977 --- /dev/null +++ b/com.streamdock.ai-monitor.sdPlugin/manifest.json @@ -0,0 +1,97 @@ +{ + "Actions": [ + { + "UUID": "com.streamdock.ai-monitor.codex", + "Icon": "static/img/codex-icon.svg", + "Name": "Codex Desktop", + "States": [ + { + "Image": "static/img/codex-icon.svg", + "TitleAlignment": "center", + "FontSize": "11" + } + ], + "Settings": { + "pollInterval": 1000 + }, + "Controllers": [ + "Keypad", + "Information" + ], + "UserTitleEnabled": true, + "SupportedInMultiActions": true, + "Tooltip": "Codex Desktop 工作流状态监控" + }, + { + "UUID": "com.streamdock.ai-monitor.claude", + "Icon": "static/img/claude-icon.svg", + "Name": "Claude Desktop", + "States": [ + { + "Image": "static/img/claude-icon.svg", + "TitleAlignment": "center", + "FontSize": "11" + } + ], + "Settings": { + "pollInterval": 1000 + }, + "Controllers": [ + "Keypad", + "Information" + ], + "UserTitleEnabled": true, + "SupportedInMultiActions": true, + "Tooltip": "Claude Desktop 工作流状态监控" + }, + { + "UUID": "com.streamdock.ai-monitor.opencode", + "Icon": "static/img/opencode-icon.svg", + "Name": "opencode", + "States": [ + { + "Image": "static/img/opencode-icon.svg", + "TitleAlignment": "center", + "FontSize": "11" + } + ], + "Settings": { + "pollInterval": 1000 + }, + "Controllers": [ + "Keypad", + "Information" + ], + "UserTitleEnabled": true, + "SupportedInMultiActions": true, + "Tooltip": "opencode 工作流状态监控" + } + ], + "SDKVersion": 1, + "Author": "StreamDock", + "Name": "AI Monitor", + "Icon": "static/img/codex-icon.svg", + "Category": "AI Monitor", + "CategoryIcon": "static/img/codex-icon.svg", + "CodePathWin": "plugin/index.js", + "CodePathMac": "plugin/index.js", + "Description": "监控 Codex Desktop / Claude Desktop / opencode AI 工作流状态", + "Version": "2.0.0", + "URL": "http://10.10.10.14:18003/v6ole/streamdock-ai-monitor", + "OS": [ + { + "Platform": "windows", + "MinimumVersion": "7" + }, + { + "Platform": "mac", + "MinimumVersion": "10.11" + } + ], + "Software": { + "MinimumVersion": "3.10.188.226" + }, + "Nodejs": { + "Version": "20" + } +} \ No newline at end of file diff --git a/claude/com.streamdock.ai-monitor.claude.sdPlugin/plugin/autofile.js b/com.streamdock.ai-monitor.sdPlugin/plugin/autofile.js similarity index 100% rename from claude/com.streamdock.ai-monitor.claude.sdPlugin/plugin/autofile.js rename to com.streamdock.ai-monitor.sdPlugin/plugin/autofile.js diff --git a/com.streamdock.ai-monitor.sdPlugin/plugin/index.js b/com.streamdock.ai-monitor.sdPlugin/plugin/index.js new file mode 100644 index 0000000..fd90a6b --- /dev/null +++ b/com.streamdock.ai-monitor.sdPlugin/plugin/index.js @@ -0,0 +1,195 @@ +const { Plugins, Actions, log } = require('./utils/plugin'); +const fs = require('fs'); +const path = require('path'); +const os = require('os'); + +const plugin = new Plugins('ai-monitor'); +const HOME = os.homedir(); +const POLL_INTERVAL = 1000; +const IDLE_TIMEOUT_MS = 3000; + +// Agent configs +var AGENTS = { + codex: { + label: 'Codex', + scanDir: path.join(HOME, '.codex', 'sessions'), + logoFile: 'codex-icon.svg', + normalize: function(obj) { + var t = obj.type || '', pt = (obj.payload || {}).type || '', it = ((obj.payload || {}).item || {}).type || ''; + if (t === 'event_msg') { + if (pt === 'task_started') return 'thinking'; + if (pt === 'task_complete') return 'done'; + if (pt === 'turn_aborted') return 'error'; + if (pt === 'agent_message') return 'typing'; + if (pt === 'user_message') return 'prompt'; + if (pt === 'web_search_end') return 'working'; + if (pt === 'agent_reasoning') return 'thinking'; + return null; + } + if (t === 'response_item') { + var nt = pt || it; + if (nt === 'function_call' || nt === 'custom_tool_call' || nt === 'function_call_output' || nt === 'custom_tool_call_output') return 'working'; + if (nt === 'web_search_call') return 'working'; + if (nt === 'reasoning') return 'thinking'; + if (nt === 'message') return 'typing'; + return null; + } + return null; + } + }, + claude: { + label: 'Claude', + scanDir: path.join(HOME, '.claude', 'projects'), + logoFile: 'claude-icon.svg', + normalize: function(obj) { + var t = obj.type || ''; + if (t === 'user') return 'prompt'; + if (t === 'assistant') { + var content = (obj.message || {}).content || []; + for (var i = 0; i < content.length; i++) { + var ct = content[i].type || ''; + if (ct === 'tool_use') return 'working'; + if (ct === 'thinking') return 'thinking'; + } + for (var j = 0; j < content.length; j++) { if ((content[j].type || '') === 'text') return 'typing'; } + return 'typing'; + } + if (t === 'system') { if (obj.subtype === 'turn_duration') return 'done'; return null; } + return null; + } + }, + opencode: { + label: 'opencode', + scanDir: path.join(HOME, '.opencode', 'sessions'), + logoFile: 'opencode-icon.svg', + normalize: function(obj) { + var t = obj.type || ''; + if (t === 'user') return 'prompt'; + if (t === 'assistant') return 'typing'; + if (t === 'tool' || t === 'tool_call') return 'working'; + if (t === 'thinking' || t === 'reasoning') return 'thinking'; + if (t === 'done' || t === 'complete') return 'done'; + if (t === 'error') return 'error'; + return null; + } + } +}; + +// Load logos as data URIs +var LOGOS = {}; +function loadLogo(filename) { + try { return 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(fs.readFileSync(path.join(__dirname, '..', 'static', 'img', filename), 'utf-8')); } + catch (_) { return ''; } +} +Object.keys(AGENTS).forEach(function(k) { LOGOS[k] = loadLogo(AGENTS[k].logoFile); }); + +// Shared display machinery +var STATUS = { + idle: { title: 'Ready', fill: '#22c55e', blink: false, breathe: false }, + prompt: { title: 'Prompt', fill: '#3b82f6', blink: false, breathe: false }, + thinking: { title: 'Thinking', fill: '#f59e0b', blink: false, breathe: true }, + working: { title: 'Working', fill: '#f59e0b', blink: true, breathe: false }, + typing: { title: 'Typing', fill: '#f59e0b', blink: false, breathe: true }, + done: { title: 'Done', fill: '#22c55e', blink: true, breathe: false }, + error: { title: 'Error', fill: '#ef4444', blink: false, breathe: false }, +}; +var PRIORITY = ['error','working','typing','thinking','prompt','done','idle']; +var states = {}; // per-context state: { agent, currentState, lastActivity, offsets, timer, logoUri } + +function makeSvg(fill, label, blink, breathe) { + var anim = ''; + if (blink) anim = ''; + else if (breathe) anim = ''; + return '' + anim + '' + label + ''; +} + +function setDisplay(ctx, state) { + var d = STATUS[state] || STATUS['idle']; + var ss = states[ctx]; + plugin.setTitle(ctx, d.title); + if (state === 'idle' && ss && ss.logoUri) plugin.setImage(ctx, ss.logoUri); + else plugin.setImage(ctx, 'data:image/svg+xml;charset=utf-8,' + makeSvg(d.fill, d.title, d.blink, d.breathe)); +} + +function setState(ctx, state) { + var ss = states[ctx]; if (!ss) return; + if (state === ss.currentState) return; + if (state === 'done') { setDisplay(ctx, 'done'); ss.currentState = 'done'; setTimeout(function() { if (states[ctx] && states[ctx].currentState === 'done') { states[ctx].currentState = 'idle'; setDisplay(ctx, 'idle'); } }, 800); } + else { ss.currentState = state; setDisplay(ctx, state); } +} + +function findFiles(rootDir, suffix) { + var results = []; + if (!fs.existsSync(rootDir)) return results; + (function walk(dir, depth) { + if (depth > 16) return; + var names; try { names = fs.readdirSync(dir); } catch (_) { return; } + for (var i = 0; i < names.length; i++) { + var full = path.join(dir, names[i]); + var st; try { st = fs.statSync(full); } catch (_) { continue; } + if (st.isDirectory()) walk(full, depth + 1); + else if (st.isFile() && names[i].endsWith(suffix)) results.push(full); + } + })(rootDir, 0); + return results; +} + +function tailFile(filePath, ctx, agent) { + var ss = states[ctx]; if (!ss) return false; + var detected = false; + try { + var stat = fs.statSync(filePath); + var off = ss.offsets[filePath] || 0; + if (stat.size < off) { ss.offsets[filePath] = 0; return false; } + if (stat.size === off) return false; + var fd = fs.openSync(filePath, 'r'); + var grow = Math.min(stat.size - off, 65536); + var buf = Buffer.alloc(grow); + fs.readSync(fd, buf, 0, grow, off); + fs.closeSync(fd); + ss.offsets[filePath] = stat.size; + var lines = buf.toString('utf-8').split('\n').filter(function(l) { return l.trim(); }); + var bestPri = PRIORITY.length, bestState = null; + for (var i = 0; i < lines.length; i++) { + try { var obj = JSON.parse(lines[i]); var s = agent.normalize(obj); if (s) { var pri = PRIORITY.indexOf(s); if (pri >= 0 && pri < bestPri) { bestPri = pri; bestState = s; } } } catch (_) {} + } + if (bestState) { setState(ctx, bestState); detected = true; } + } catch (_) { delete ss.offsets[filePath]; } + return detected; +} + +function poll(ctx) { + var ss = states[ctx]; if (!ss || !ss.agent) return; + var detected = false; + var files = findFiles(ss.agent.scanDir, '.jsonl'); + files.sort(function(a,b) { return fs.statSync(b).mtimeMs - fs.statSync(a).mtimeMs; }); + for (var i = 0; i < Math.min(files.length, 3); i++) { if (tailFile(files[i], ctx, ss.agent)) detected = true; } + if (detected) ss.lastActivity = Date.now(); + if (ss.currentState !== 'idle' && ss.currentState !== 'done' && Date.now() - ss.lastActivity > IDLE_TIMEOUT_MS) { setState(ctx, 'idle'); } +} + +function makeActionFn(agentKey) { + var agent = AGENTS[agentKey]; + return { + default: { pollInterval: POLL_INTERVAL }, + _willAppear: function(data) { + var ctx = data.context; + var logoUri = LOGOS[agentKey] || ''; + states[ctx] = { agent: agent, currentState: 'idle', lastActivity: Date.now(), offsets: {}, timer: null, logoUri: logoUri }; + setDisplay(ctx, 'idle'); + poll(ctx); + states[ctx].timer = setInterval(function() { poll(ctx); }, POLL_INTERVAL); + }, + _willDisappear: function(data) { + var ctx = data.context; + if (states[ctx] && states[ctx].timer) { clearInterval(states[ctx].timer); } + delete states[ctx]; + }, + keyUp: function(data) { poll(data.context); }, + }; +} + +// Register each agent as a separate action +plugin.codex = new Actions(makeActionFn('codex')); +plugin.claude = new Actions(makeActionFn('claude')); +plugin.opencode = new Actions(makeActionFn('opencode')); \ No newline at end of file diff --git a/codex/com.streamdock.ai-monitor.codex.sdPlugin/plugin/package-lock.json b/com.streamdock.ai-monitor.sdPlugin/plugin/package-lock.json similarity index 98% rename from codex/com.streamdock.ai-monitor.codex.sdPlugin/plugin/package-lock.json rename to com.streamdock.ai-monitor.sdPlugin/plugin/package-lock.json index 882a5a9..e61075b 100644 --- a/codex/com.streamdock.ai-monitor.codex.sdPlugin/plugin/package-lock.json +++ b/com.streamdock.ai-monitor.sdPlugin/plugin/package-lock.json @@ -1,12 +1,12 @@ { - "name": "ai-monitor-codex", - "version": "1.0.0", + "name": "ai-monitor", + "version": "2.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "ai-monitor-codex", - "version": "1.0.0", + "name": "ai-monitor", + "version": "2.0.0", "license": "MIT", "dependencies": { "fs-extra": "^11.3.0", diff --git a/opencode/com.streamdock.ai-monitor.opencode.sdPlugin/plugin/package.json b/com.streamdock.ai-monitor.sdPlugin/plugin/package.json similarity index 59% rename from opencode/com.streamdock.ai-monitor.opencode.sdPlugin/plugin/package.json rename to com.streamdock.ai-monitor.sdPlugin/plugin/package.json index 0f022e0..aaa59c4 100644 --- a/opencode/com.streamdock.ai-monitor.opencode.sdPlugin/plugin/package.json +++ b/com.streamdock.ai-monitor.sdPlugin/plugin/package.json @@ -1,9 +1,9 @@ { - "name": "ai-monitor-opencode", - "version": "1.0.0", + "name": "ai-monitor", + "version": "2.0.0", "author": "StreamDock", "main": "index.js", - "description": "opencode AI workflow status monitor", + "description": "AI agent workflow status monitor", "license": "MIT", "dependencies": { "fs-extra": "^11.3.0", diff --git a/claude/com.streamdock.ai-monitor.claude.sdPlugin/plugin/utils/plugin.js b/com.streamdock.ai-monitor.sdPlugin/plugin/utils/plugin.js similarity index 100% rename from claude/com.streamdock.ai-monitor.claude.sdPlugin/plugin/utils/plugin.js rename to com.streamdock.ai-monitor.sdPlugin/plugin/utils/plugin.js diff --git a/claude/com.streamdock.ai-monitor.claude.sdPlugin/static/img/claude-icon.svg b/com.streamdock.ai-monitor.sdPlugin/static/img/claude-icon.svg similarity index 100% rename from claude/com.streamdock.ai-monitor.claude.sdPlugin/static/img/claude-icon.svg rename to com.streamdock.ai-monitor.sdPlugin/static/img/claude-icon.svg diff --git a/codex/com.streamdock.ai-monitor.codex.sdPlugin/static/img/codex-icon.svg b/com.streamdock.ai-monitor.sdPlugin/static/img/codex-icon.svg similarity index 100% rename from codex/com.streamdock.ai-monitor.codex.sdPlugin/static/img/codex-icon.svg rename to com.streamdock.ai-monitor.sdPlugin/static/img/codex-icon.svg diff --git a/opencode/com.streamdock.ai-monitor.opencode.sdPlugin/static/img/opencode-icon.svg b/com.streamdock.ai-monitor.sdPlugin/static/img/opencode-icon.svg similarity index 100% rename from opencode/com.streamdock.ai-monitor.opencode.sdPlugin/static/img/opencode-icon.svg rename to com.streamdock.ai-monitor.sdPlugin/static/img/opencode-icon.svg diff --git a/com.streamdock.ai-monitor.sdPlugin/zh_CN.json b/com.streamdock.ai-monitor.sdPlugin/zh_CN.json new file mode 100644 index 0000000..50a7b19 --- /dev/null +++ b/com.streamdock.ai-monitor.sdPlugin/zh_CN.json @@ -0,0 +1,4 @@ +{ + "Name": "AI 监控", + "Description": "在 N4 上实时显示 AI Agent 工作流状态" +} \ No newline at end of file diff --git a/opencode/com.streamdock.ai-monitor.opencode.sdPlugin/.gitignore b/opencode/com.streamdock.ai-monitor.opencode.sdPlugin/.gitignore deleted file mode 100644 index ca5cc30..0000000 --- a/opencode/com.streamdock.ai-monitor.opencode.sdPlugin/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -node_modules/ -*.log -build/ \ No newline at end of file diff --git a/opencode/com.streamdock.ai-monitor.opencode.sdPlugin/en.json b/opencode/com.streamdock.ai-monitor.opencode.sdPlugin/en.json deleted file mode 100644 index 274858d..0000000 --- a/opencode/com.streamdock.ai-monitor.opencode.sdPlugin/en.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "Name": "opencode Monitor", - "Description": "Monitor opencode AI workflow status" -} \ No newline at end of file diff --git a/opencode/com.streamdock.ai-monitor.opencode.sdPlugin/manifest.json b/opencode/com.streamdock.ai-monitor.opencode.sdPlugin/manifest.json deleted file mode 100644 index 8ccc5ec..0000000 --- a/opencode/com.streamdock.ai-monitor.opencode.sdPlugin/manifest.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "Actions": [ - { - "Icon": "static/img/opencode-icon.svg", - "Name": "opencode", - "States": [ - { - "Image": "static/img/opencode-icon.svg", - "TitleAlignment": "center", - "FontSize": "11" - } - ], - "Settings": { - "pollInterval": 1000 - }, - "Controllers": [ - "Keypad", - "Information" - ], - "UserTitleEnabled": true, - "SupportedInMultiActions": true, - "Tooltip": "opencode 工作流状态监控", - "UUID": "com.streamdock.ai-monitor.opencode" - } - ], - "SDKVersion": 1, - "Author": "StreamDock", - "Name": "AI Monitor - opencode", - "Icon": "static/img/opencode-icon.svg", - "Category": "AI Monitor", - "CategoryIcon": "static/img/opencode-icon.svg", - "CodePathWin": "plugin/index.js", - "CodePathMac": "plugin/index.js", - "Description": "监控 opencode AI 工作流状态,在 N4 按钮上实时显示", - "Version": "1.0.0", - "URL": "http://10.10.10.14:18003/v6ole/streamdock-ai-monitor", - "OS": [ - { - "Platform": "windows", - "MinimumVersion": "7" - }, - { - "Platform": "mac", - "MinimumVersion": "10.11" - } - ], - "Software": { - "MinimumVersion": "3.10.188.226" - }, - "Nodejs": { - "Version": "20" - } -} \ No newline at end of file diff --git a/opencode/com.streamdock.ai-monitor.opencode.sdPlugin/plugin/autofile.js b/opencode/com.streamdock.ai-monitor.opencode.sdPlugin/plugin/autofile.js deleted file mode 100644 index 38c45b6..0000000 --- a/opencode/com.streamdock.ai-monitor.opencode.sdPlugin/plugin/autofile.js +++ /dev/null @@ -1,47 +0,0 @@ -const path = require('path'); -const fs = require('fs-extra'); - -console.log('开始执行自动化构建...'); - -const currentDir = __dirname; - -// 获取父文件夹的路径 -const parentDir = path.join(currentDir, '..'); -// 获取父文件夹的名称 -const PluginName = path.basename(parentDir); - - -const PluginPath = path.join(process.env.APPDATA, 'HotSpot/StreamDock/plugins', PluginName); - -try { - // 删除旧的插件目录 - fs.removeSync(PluginPath); - - // 确保目标目录存在 - fs.ensureDirSync(path.dirname(PluginPath)); - - // 复制当前目录到目标路径,排除 node_modules - fs.copySync(path.resolve(__dirname, '..'), PluginPath, { - filter: (src) => { - const relativePath = path.relative(path.resolve(__dirname, '..'), src); - // 排除 'node_modules' 和 '.git' 目录及其子文件 - return !relativePath.startsWith('plugin\\node_modules') - &&!relativePath.startsWith('plugin\\index.js') - &&!relativePath.startsWith('plugin\\package.json') - &&!relativePath.startsWith('plugin\\package-lock.json') - &&!relativePath.startsWith('plugin\\pnpm-lock.yaml') - &&!relativePath.startsWith('plugin\\yarn.lock') - &&!relativePath.startsWith('plugin\\build') - &&!relativePath.startsWith('plugin\\log') - &&!relativePath.startsWith('.git') - &&!relativePath.startsWith('.vscode'); - } - }); - - fs.copySync( path.join(__dirname, "build"), path.join(PluginPath,'plugin')) - - console.log(`插件 "${PluginName}" 已成功复制到 "${PluginPath}"`); - console.log('构建成功-------------'); -} catch (err) { - console.error(`复制出错 "${PluginName}":`, err); -} \ No newline at end of file diff --git a/opencode/com.streamdock.ai-monitor.opencode.sdPlugin/plugin/index.js b/opencode/com.streamdock.ai-monitor.opencode.sdPlugin/plugin/index.js deleted file mode 100644 index 7f2b554..0000000 --- a/opencode/com.streamdock.ai-monitor.opencode.sdPlugin/plugin/index.js +++ /dev/null @@ -1,145 +0,0 @@ -const { Plugins, Actions, log } = require('./utils/plugin'); -const fs = require('fs'); -const path = require('path'); -const os = require('os'); - -const plugin = new Plugins('ai-monitor-opencode'); -const timers = {}; -const sessionOffsets = {}; - -const HOME = os.homedir(); -const SCAN_DIR = path.join(HOME, '.opencode', 'sessions'); -const POLL_INTERVAL = 1000; -const IDLE_TIMEOUT_MS = 3000; - -// Load logo -var logoUri = ''; -try { - var logoPath = path.join(__dirname, '..', 'static', 'img', 'opencode-icon.svg'); - logoUri = 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(fs.readFileSync(logoPath, 'utf-8')); -} catch (_) {} - -var STATUS = { - idle: { title: 'Ready', fill: '#22c55e', blink: false, breathe: false }, - prompt: { title: 'Prompt', fill: '#3b82f6', blink: false, breathe: false }, - thinking: { title: 'Thinking', fill: '#f59e0b', blink: false, breathe: true }, - working: { title: 'Working', fill: '#f59e0b', blink: true, breathe: false }, - typing: { title: 'Typing', fill: '#f59e0b', blink: false, breathe: true }, - done: { title: 'Done', fill: '#22c55e', blink: true, breathe: false }, - error: { title: 'Error', fill: '#ef4444', blink: false, breathe: false }, -}; - -var PRIORITY = ['error', 'working', 'typing', 'thinking', 'prompt', 'done', 'idle']; -var currentCtx = null, currentState = 'idle', lastActivityTime = 0; -var animationHandle = null; - -function makeSvg(fill, label, blink, breathe) { - var anim = ''; - if (blink) anim = ''; - else if (breathe) anim = ''; - return '' + anim + '' + label + ''; -} - -function setDisplay(ctx, state) { - var d = STATUS[state] || STATUS['idle']; - plugin.setTitle(ctx, d.title); - if (state === 'idle' && logoUri) plugin.setImage(ctx, logoUri); - else plugin.setImage(ctx, 'data:image/svg+xml;charset=utf-8,' + makeSvg(d.fill, d.title, d.blink, d.breathe)); -} - -function animateTick() { - if (!currentCtx) return; - var d = STATUS[currentState] || STATUS['idle']; - if (d.blink || d.breathe) { - var now = Date.now(), period = d.blink ? 800 : 2000, phase = (now % period) / period; - var alpha = d.blink ? (phase < 0.5 ? 1 : 0.2) : 0.4 + 0.6 * Math.sin(phase * Math.PI); - var r = parseInt(d.fill.slice(1,3),16), g = parseInt(d.fill.slice(3,5),16), b = parseInt(d.fill.slice(5,7),16); - var color = 'rgb(' + Math.round(16+(r-16)*alpha) + ',' + Math.round(16+(g-16)*alpha) + ',' + Math.round(16+(b-16)*alpha) + ')'; - var svg = '' + d.title + ''; - plugin.setImage(currentCtx, 'data:image/svg+xml;charset=utf-8,' + svg); - } -} - -function setState(ctx, state) { - if (state === currentState) return; - if (state === 'done') { setDisplay(ctx, 'done'); currentState = 'done'; setTimeout(function() { if (currentState === 'done') { currentState = 'idle'; setDisplay(ctx, 'idle'); } }, 800); } - else { currentState = state; setDisplay(ctx, state); } -} - -function normalize(obj) { - var t = obj.type || ''; - if (t === 'user') return 'prompt'; - if (t === 'assistant') return 'typing'; - if (t === 'tool' || t === 'tool_call') return 'working'; - if (t === 'thinking' || t === 'reasoning') return 'thinking'; - if (t === 'done' || t === 'complete') return 'done'; - if (t === 'error') return 'error'; - return null; -} - -function findFiles(rootDir, suffix) { - var results = []; - if (!fs.existsSync(rootDir)) return results; - (function walk(dir, depth) { - if (depth > 16) return; - var names; try { names = fs.readdirSync(dir); } catch (_) { return; } - for (var i = 0; i < names.length; i++) { - var full = path.join(dir, names[i]); - var st; try { st = fs.statSync(full); } catch (_) { continue; } - if (st.isDirectory()) walk(full, depth + 1); - else if (st.isFile() && names[i].endsWith(suffix)) results.push(full); - } - })(rootDir, 0); - return results; -} - -function tailFile(filePath, ctx) { - var detected = false; - try { - var stat = fs.statSync(filePath); - var off = sessionOffsets[filePath] || 0; - if (stat.size < off) { sessionOffsets[filePath] = 0; return false; } - if (stat.size === off) return false; - var fd = fs.openSync(filePath, 'r'); - var grow = Math.min(stat.size - off, 65536); - var buf = Buffer.alloc(grow); - fs.readSync(fd, buf, 0, grow, off); - fs.closeSync(fd); - sessionOffsets[filePath] = stat.size; - var lines = buf.toString('utf-8').split('\n').filter(function(l) { return l.trim(); }); - var bestPri = PRIORITY.length, bestState = null; - for (var i = 0; i < lines.length; i++) { - try { var obj = JSON.parse(lines[i]); var s = normalize(obj); if (s) { var pri = PRIORITY.indexOf(s); if (pri >= 0 && pri < bestPri) { bestPri = pri; bestState = s; } } } catch (_) {} - } - if (bestState) { setState(ctx, bestState); detected = true; } - } catch (_) { delete sessionOffsets[filePath]; } - return detected; -} - -function poll(ctx) { - var detected = false; - var files = findFiles(SCAN_DIR, '.jsonl'); - files.sort(function(a,b) { return fs.statSync(b).mtimeMs - fs.statSync(a).mtimeMs; }); - for (var i = 0; i < Math.min(files.length, 3); i++) { if (tailFile(files[i], ctx)) detected = true; } - if (detected) lastActivityTime = Date.now(); - if (currentState !== 'idle' && currentState !== 'done' && Date.now() - lastActivityTime > IDLE_TIMEOUT_MS) { setState(ctx, 'idle'); } -} - -plugin.opencode = new Actions({ - default: { pollInterval: POLL_INTERVAL }, - _willAppear: function(data) { - var ctx = data.context; - currentCtx = ctx; currentState = 'idle'; lastActivityTime = Date.now(); - setDisplay(ctx, 'idle'); - poll(ctx); - timers[ctx] = setInterval(function() { poll(ctx); }, POLL_INTERVAL); - if (!animationHandle) animationHandle = setInterval(animateTick, 200); - }, - _willDisappear: function(data) { - var ctx = data.context; - if (timers[ctx]) { clearInterval(timers[ctx]); delete timers[ctx]; } - if (currentCtx === ctx) { currentCtx = null; currentState = 'idle'; } - if (Object.keys(timers).length === 0 && animationHandle) { clearInterval(animationHandle); animationHandle = null; } - }, - keyUp: function(data) { poll(data.context); } -}); \ No newline at end of file diff --git a/opencode/com.streamdock.ai-monitor.opencode.sdPlugin/plugin/package-lock.json b/opencode/com.streamdock.ai-monitor.opencode.sdPlugin/plugin/package-lock.json deleted file mode 100644 index b58c86f..0000000 --- a/opencode/com.streamdock.ai-monitor.opencode.sdPlugin/plugin/package-lock.json +++ /dev/null @@ -1,186 +0,0 @@ -{ - "name": "ai-monitor-opencode", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "ai-monitor-opencode", - "version": "1.0.0", - "license": "MIT", - "dependencies": { - "fs-extra": "^11.3.0", - "log4js": "^6.9.1", - "ws": "^8.14.2" - } - }, - "node_modules/date-format": { - "version": "4.0.14", - "resolved": "https://registry.npmjs.org/date-format/-/date-format-4.0.14.tgz", - "integrity": "sha512-39BOQLs9ZjKh0/patS9nrT8wc3ioX3/eA/zgbKNopnF2wCqJEoxywwwElATYvRsXdnOxA/OQeQoFZ3rFjVajhg==", - "license": "MIT", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/flatted": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", - "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", - "license": "ISC" - }, - "node_modules/fs-extra": { - "version": "11.3.5", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.5.tgz", - "integrity": "sha512-eKpRKAovdpZtR1WopLHxlBWvAgPny3c4gX1G5Jhwmmw4XJj0ifSD5qB5TOo8hmA0wlRKDAOAhEE1yVPgs6Fgcg==", - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=14.14" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "license": "ISC" - }, - "node_modules/jsonfile": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", - "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", - "license": "MIT", - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/log4js": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/log4js/-/log4js-6.9.1.tgz", - "integrity": "sha512-1somDdy9sChrr9/f4UlzhdaGfDR2c/SaD2a4T7qEkG4jTS57/B3qmnjLYePwQ8cqWnUHZI0iAKxMBpCZICiZ2g==", - "license": "Apache-2.0", - "dependencies": { - "date-format": "^4.0.14", - "debug": "^4.3.4", - "flatted": "^3.2.7", - "rfdc": "^1.3.0", - "streamroller": "^3.1.5" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, - "node_modules/rfdc": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", - "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", - "license": "MIT" - }, - "node_modules/streamroller": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-3.1.5.tgz", - "integrity": "sha512-KFxaM7XT+irxvdqSP1LGLgNWbYN7ay5owZ3r/8t77p+EtSUAfUgtl7be3xtqtOmGUl9K9YPO2ca8133RlTjvKw==", - "license": "MIT", - "dependencies": { - "date-format": "^4.0.14", - "debug": "^4.3.4", - "fs-extra": "^8.1.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/streamroller/node_modules/fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - }, - "engines": { - "node": ">=6 <7 || >=8" - } - }, - "node_modules/streamroller/node_modules/jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", - "license": "MIT", - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/streamroller/node_modules/universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", - "license": "MIT", - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "license": "MIT", - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/ws": { - "version": "8.21.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", - "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", - "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - } - } -} diff --git a/opencode/com.streamdock.ai-monitor.opencode.sdPlugin/plugin/utils/plugin.js b/opencode/com.streamdock.ai-monitor.opencode.sdPlugin/plugin/utils/plugin.js deleted file mode 100644 index 673de5c..0000000 --- a/opencode/com.streamdock.ai-monitor.opencode.sdPlugin/plugin/utils/plugin.js +++ /dev/null @@ -1,213 +0,0 @@ -// 配置日志文件 -const now = new Date(); -const log = require('log4js').configure({ - appenders: { - file: { type: 'file', filename: `./log/${now.getFullYear()}.${now.getMonth() + 1}.${now.getDate()}.log` } - }, - categories: { - default: { appenders: ['file'], level: 'info' } - } -}).getLogger(); - -//################################################## -//##################全局异常捕获##################### -process.on('uncaughtException', (error) => { - log.error('Uncaught Exception:', error); -}); -process.on('unhandledRejection', (reason) => { - log.error('Unhandled Rejection:', reason); -}); -//################################################## -//################################################## - - -// 插件类 -const ws = require('ws'); -class Plugins { - static language = JSON.parse(process.argv[9]).application.language; - static globalSettings = {}; - getGlobalSettingsFlag = true; - constructor() { - if (Plugins.instance) { - return Plugins.instance; - } - // log.info("process.argv", process.argv); - this.ws = new ws("ws://127.0.0.1:" + process.argv[3]); - this.ws.on('open', () => this.ws.send(JSON.stringify({ uuid: process.argv[5], event: process.argv[7] }))); - this.ws.on('close', process.exit); - this.ws.on('message', e => { - if (this.getGlobalSettingsFlag) { - // 只获取一次 - this.getGlobalSettingsFlag = false; - this.getGlobalSettings(); - } - const data = JSON.parse(e.toString()); - const action = data.action?.split('.').pop(); - this[action]?.[data.event]?.(data); - if (data.event === 'didReceiveGlobalSettings') { - Plugins.globalSettings = data.payload.settings; - } - this[data.event]?.(data); - }); - Plugins.instance = this; - } - - setGlobalSettings(payload) { - Plugins.globalSettings = payload; - this.ws.send(JSON.stringify({ - event: "setGlobalSettings", - context: process.argv[5], payload - })); - } - - getGlobalSettings() { - this.ws.send(JSON.stringify({ - event: "getGlobalSettings", - context: process.argv[5], - })); - } - // 设置标题 - setTitle(context, str, row = 0, num = 6) { - let newStr = null; - if (row && str) { - let nowRow = 1, strArr = str.split(''); - strArr.forEach((item, index) => { - if (nowRow < row && index >= nowRow * num) { nowRow++; newStr += '\n'; } - if (nowRow <= row && index < nowRow * num) { newStr += item; } - }); - if (strArr.length > row * num) { newStr = newStr.substring(0, newStr.length - 1); newStr += '..'; } - } - this.ws.send(JSON.stringify({ - event: "setTitle", - context, payload: { - target: 0, - title: newStr || str + '' - } - })); - } - // 设置背景 - setImage(context, url) { - this.ws.send(JSON.stringify({ - event: "setImage", - context, payload: { - target: 0, - image: url - } - })); - } - // 设置状态 - setState(context, state) { - this.ws.send(JSON.stringify({ - event: "setState", - context, payload: { state } - })); - } - // 保存持久化数据 - setSettings(context, payload) { - this.ws.send(JSON.stringify({ - event: "setSettings", - context, payload - })); - } - - // 在按键上展示警告 - showAlert(context) { - this.ws.send(JSON.stringify({ - event: "showAlert", - context - })); - } - - // 在按键上展示成功 - showOk(context) { - this.ws.send(JSON.stringify({ - event: "showOk", - context - })); - } - // 发送给属性检测器 - sendToPropertyInspector(payload) { - this.ws.send(JSON.stringify({ - action: Actions.currentAction, - context: Actions.currentContext, - payload, event: "sendToPropertyInspector" - })); - } - // 用默认浏览器打开网页 - openUrl(url) { - this.ws.send(JSON.stringify({ - event: "openUrl", - payload: { url } - })); - } -}; - -// 操作类 -class Actions { - constructor(data) { - this.data = {}; - this.default = {}; - Object.assign(this, data); - } - // 属性检查器显示时 - static currentAction = null; - static currentContext = null; - static actions = {}; - propertyInspectorDidAppear(data) { - Actions.currentAction = data.action; - Actions.currentContext = data.context; - this._propertyInspectorDidAppear?.(data); - } - // 初始化数据 - willAppear(data) { - Plugins.globalContext = data.context; - Actions.actions[data.context] = data.action - const { context, payload: { settings } } = data; - this.data[context] = Object.assign({ ...this.default }, settings); - this._willAppear?.(data); - } - - didReceiveSettings(data) { - this.data[data.context] = data.payload.settings; - this._didReceiveSettings?.(data); - } - // 行动销毁 - willDisappear(data) { - this._willDisappear?.(data); - delete this.data[data.context]; - } -} - -class EventEmitter { - constructor() { - this.events = {}; - } - - // 订阅事件 - subscribe(event, listener) { - if (!this.events[event]) { - this.events[event] = []; - } - this.events[event].push(listener); - } - - // 取消订阅 - unsubscribe(event, listenerToRemove) { - if (!this.events[event]) return; - - this.events[event] = this.events[event].filter(listener => listener !== listenerToRemove); - } - - // 发布事件 - emit(event, data) { - if (!this.events[event]) return; - this.events[event].forEach(listener => listener(data)); - } -} - -module.exports = { - log, - Plugins, - Actions, - EventEmitter -}; \ No newline at end of file diff --git a/opencode/com.streamdock.ai-monitor.opencode.sdPlugin/zh_CN.json b/opencode/com.streamdock.ai-monitor.opencode.sdPlugin/zh_CN.json deleted file mode 100644 index f10ed77..0000000 --- a/opencode/com.streamdock.ai-monitor.opencode.sdPlugin/zh_CN.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "Name": "opencode 监控", - "Description": "监控 opencode AI 工作流状态" -} \ No newline at end of file diff --git a/shared/plugin.js b/shared/plugin.js deleted file mode 100644 index 673de5c..0000000 --- a/shared/plugin.js +++ /dev/null @@ -1,213 +0,0 @@ -// 配置日志文件 -const now = new Date(); -const log = require('log4js').configure({ - appenders: { - file: { type: 'file', filename: `./log/${now.getFullYear()}.${now.getMonth() + 1}.${now.getDate()}.log` } - }, - categories: { - default: { appenders: ['file'], level: 'info' } - } -}).getLogger(); - -//################################################## -//##################全局异常捕获##################### -process.on('uncaughtException', (error) => { - log.error('Uncaught Exception:', error); -}); -process.on('unhandledRejection', (reason) => { - log.error('Unhandled Rejection:', reason); -}); -//################################################## -//################################################## - - -// 插件类 -const ws = require('ws'); -class Plugins { - static language = JSON.parse(process.argv[9]).application.language; - static globalSettings = {}; - getGlobalSettingsFlag = true; - constructor() { - if (Plugins.instance) { - return Plugins.instance; - } - // log.info("process.argv", process.argv); - this.ws = new ws("ws://127.0.0.1:" + process.argv[3]); - this.ws.on('open', () => this.ws.send(JSON.stringify({ uuid: process.argv[5], event: process.argv[7] }))); - this.ws.on('close', process.exit); - this.ws.on('message', e => { - if (this.getGlobalSettingsFlag) { - // 只获取一次 - this.getGlobalSettingsFlag = false; - this.getGlobalSettings(); - } - const data = JSON.parse(e.toString()); - const action = data.action?.split('.').pop(); - this[action]?.[data.event]?.(data); - if (data.event === 'didReceiveGlobalSettings') { - Plugins.globalSettings = data.payload.settings; - } - this[data.event]?.(data); - }); - Plugins.instance = this; - } - - setGlobalSettings(payload) { - Plugins.globalSettings = payload; - this.ws.send(JSON.stringify({ - event: "setGlobalSettings", - context: process.argv[5], payload - })); - } - - getGlobalSettings() { - this.ws.send(JSON.stringify({ - event: "getGlobalSettings", - context: process.argv[5], - })); - } - // 设置标题 - setTitle(context, str, row = 0, num = 6) { - let newStr = null; - if (row && str) { - let nowRow = 1, strArr = str.split(''); - strArr.forEach((item, index) => { - if (nowRow < row && index >= nowRow * num) { nowRow++; newStr += '\n'; } - if (nowRow <= row && index < nowRow * num) { newStr += item; } - }); - if (strArr.length > row * num) { newStr = newStr.substring(0, newStr.length - 1); newStr += '..'; } - } - this.ws.send(JSON.stringify({ - event: "setTitle", - context, payload: { - target: 0, - title: newStr || str + '' - } - })); - } - // 设置背景 - setImage(context, url) { - this.ws.send(JSON.stringify({ - event: "setImage", - context, payload: { - target: 0, - image: url - } - })); - } - // 设置状态 - setState(context, state) { - this.ws.send(JSON.stringify({ - event: "setState", - context, payload: { state } - })); - } - // 保存持久化数据 - setSettings(context, payload) { - this.ws.send(JSON.stringify({ - event: "setSettings", - context, payload - })); - } - - // 在按键上展示警告 - showAlert(context) { - this.ws.send(JSON.stringify({ - event: "showAlert", - context - })); - } - - // 在按键上展示成功 - showOk(context) { - this.ws.send(JSON.stringify({ - event: "showOk", - context - })); - } - // 发送给属性检测器 - sendToPropertyInspector(payload) { - this.ws.send(JSON.stringify({ - action: Actions.currentAction, - context: Actions.currentContext, - payload, event: "sendToPropertyInspector" - })); - } - // 用默认浏览器打开网页 - openUrl(url) { - this.ws.send(JSON.stringify({ - event: "openUrl", - payload: { url } - })); - } -}; - -// 操作类 -class Actions { - constructor(data) { - this.data = {}; - this.default = {}; - Object.assign(this, data); - } - // 属性检查器显示时 - static currentAction = null; - static currentContext = null; - static actions = {}; - propertyInspectorDidAppear(data) { - Actions.currentAction = data.action; - Actions.currentContext = data.context; - this._propertyInspectorDidAppear?.(data); - } - // 初始化数据 - willAppear(data) { - Plugins.globalContext = data.context; - Actions.actions[data.context] = data.action - const { context, payload: { settings } } = data; - this.data[context] = Object.assign({ ...this.default }, settings); - this._willAppear?.(data); - } - - didReceiveSettings(data) { - this.data[data.context] = data.payload.settings; - this._didReceiveSettings?.(data); - } - // 行动销毁 - willDisappear(data) { - this._willDisappear?.(data); - delete this.data[data.context]; - } -} - -class EventEmitter { - constructor() { - this.events = {}; - } - - // 订阅事件 - subscribe(event, listener) { - if (!this.events[event]) { - this.events[event] = []; - } - this.events[event].push(listener); - } - - // 取消订阅 - unsubscribe(event, listenerToRemove) { - if (!this.events[event]) return; - - this.events[event] = this.events[event].filter(listener => listener !== listenerToRemove); - } - - // 发布事件 - emit(event, data) { - if (!this.events[event]) return; - this.events[event].forEach(listener => listener(data)); - } -} - -module.exports = { - log, - Plugins, - Actions, - EventEmitter -}; \ No newline at end of file