diff --git a/com.streamdock.codex.monitor.sdPlugin/manifest.json b/com.streamdock.codex.monitor.sdPlugin/manifest.json
index f413bbb..95569ad 100644
--- a/com.streamdock.codex.monitor.sdPlugin/manifest.json
+++ b/com.streamdock.codex.monitor.sdPlugin/manifest.json
@@ -13,7 +13,7 @@
],
"Settings": {
"pollInterval": 1000,
- "showHealth": true
+ "agent": "codex"
},
"Controllers": ["Keypad", "Information"],
"UserTitleEnabled": true,
diff --git a/com.streamdock.codex.monitor.sdPlugin/plugin/index.js b/com.streamdock.codex.monitor.sdPlugin/plugin/index.js
index 88887f1..5b082c4 100644
--- a/com.streamdock.codex.monitor.sdPlugin/plugin/index.js
+++ b/com.streamdock.codex.monitor.sdPlugin/plugin/index.js
@@ -3,52 +3,88 @@ const fs = require('fs');
const path = require('path');
const os = require('os');
-const plugin = new Plugins('codex-monitor');
+const plugin = new Plugins('ai-monitor');
const timers = {};
const sessionOffsets = {};
-const healthOffsets = {};
-
-// ─── 直接调试日志 ──────────────────────────────────────
const DEBUG_LOG = path.join(__dirname, '..', 'debug.log');
-function dlog(msg) {
- try { fs.appendFileSync(DEBUG_LOG, '[' + new Date().toISOString() + '] ' + msg + '\n'); } catch (_) {}
-}
-dlog('=== Plugin started v6 ===');
+function dlog(msg) { try { fs.appendFileSync(DEBUG_LOG, '[' + new Date().toISOString() + '] ' + msg + '\n'); } catch (_) {} }
+dlog('=== AI Monitor v7 multi-agent ===');
-// ─── 路径配置 ───────────────────────────────────────────
const HOME = os.homedir();
-const HEALTH_MARKER = 'app_server_connection.state_changed';
const DEFAULT_POLL = 1000;
const DONE_CUE_MS = 800;
const COOLDOWN_MS = 300;
const IDLE_TIMEOUT_MS = 3000;
-// ─── 简单递归文件查找 ──────────────────────────────────
-function findFiles(rootDir, suffix, maxDepth) {
- var results = [];
- maxDepth = maxDepth || 16;
- if (!fs.existsSync(rootDir)) return results;
- (function walk(dir, depth) {
- if (depth > maxDepth) return;
- var names;
- try { names = fs.readdirSync(dir); } catch (_) { return; }
- for (var i = 0; i < names.length; i++) {
- var name = names[i];
- var full = path.join(dir, name);
- var st;
- try { st = fs.statSync(full); } catch (_) { continue; }
- if (st.isDirectory()) walk(full, depth + 1);
- else if (st.isFile() && name.endsWith(suffix)) results.push(full);
+var AGENTS = {
+ codex: {
+ label: 'Codex',
+ scanDir: path.join(HOME, '.codex', 'sessions'),
+ fileSuffix: '.jsonl',
+ normalize: function(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;
+ }
+ },
+ claude: {
+ label: 'Claude',
+ scanDir: path.join(HOME, '.claude', 'projects'),
+ fileSuffix: '.jsonl',
+ 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';
}
- })(rootDir, 0);
- return results;
-}
-
-// ─── 状态映射 ───────────────────────────────────────────
-var STATE = {
- IDLE: 'idle', PROMPT: 'prompt', THINK: 'thinking', WORK: 'working',
- TYPING: 'typing', DONE: 'done', ERROR: 'error', WAITING: 'waiting',
- RECONNECTING: 'reconnecting', RECOVERED: 'recovered'
+ 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; }
+ if (t === 'error' || t === 'api_error') return 'error';
+ return null;
+ }
+ },
+ opencode: {
+ label: 'opencode',
+ scanDir: path.join(HOME, '.opencode', 'sessions'),
+ fileSuffix: '.jsonl',
+ 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;
+ }
+ }
};
var STATUS_DISPLAY = {};
@@ -63,258 +99,147 @@ STATUS_DISPLAY['waiting'] = { title: 'Waiting', fill: '#ef4444', blink: true, br
STATUS_DISPLAY['reconnecting'] = { title: 'Reconnect', fill: '#ef4444', blink: true, breathe: false };
STATUS_DISPLAY['recovered'] = { title: 'Online', fill: '#22c55e', blink: false, breathe: false };
-// 优先级: ERROR > WORK > TYPING > THINK > PROMPT > DONE > IDLE
-var STATE_PRIORITY = ['error', 'waiting', 'reconnecting', 'working', 'typing', 'thinking', 'prompt', 'done', 'idle'];
+var STATE_PRIORITY = ['error','waiting','reconnecting','working','typing','thinking','prompt','done','idle'];
+var currentCtx = null, currentState = 'idle', lastHwSet = 0, lastActivityTime = 0;
+var animationHandle = null, currentAgent = null;
function makeSvg(fill, label, blink, breathe) {
- var anim = '';
- if (blink) anim = '';
- else if (breathe) anim = '';
- return '';
+ var anim = '';
+ if (blink) anim = '';
+ else if (breathe) anim = '';
+ return '';
}
function setDisplay(context, state) {
- var d = STATUS_DISPLAY[state] || STATUS_DISPLAY['idle'];
- plugin.setTitle(context, d.title);
- plugin.setImage(context, 'data:image/svg+xml;charset=utf-8,' + makeSvg(d.fill, d.title, d.blink, d.breathe));
+ var d = STATUS_DISPLAY[state] || STATUS_DISPLAY['idle'];
+ plugin.setTitle(context, d.title);
+ plugin.setImage(context, 'data:image/svg+xml;charset=utf-8,' + makeSvg(d.fill, d.title, d.blink, d.breathe));
}
-// ─── 状态管理 ──────────────────────────────────────────
-var currentCtx = null, currentState = 'idle', lastHwSet = 0, lastActivityTime = 0;
-var animationHandle = null;
-
function animateTick() {
- if (!currentCtx) return;
- var d = STATUS_DISPLAY[currentState] || STATUS_DISPLAY['idle'];
- if (d.blink || d.breathe) {
- var now = Date.now();
- var period = d.blink ? 800 : 2000;
- var 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 = '';
- plugin.setImage(currentCtx, 'data:image/svg+xml;charset=utf-8,' + svg);
- }
+ if (!currentCtx) return;
+ var d = STATUS_DISPLAY[currentState] || STATUS_DISPLAY['idle'];
+ if (d.blink || d.breathe) {
+ var now = Date.now(); var period = d.blink ? 800 : 2000; var 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 = '';
+ plugin.setImage(currentCtx, 'data:image/svg+xml;charset=utf-8,' + svg);
+ }
}
function setState(context, state) {
- if (state === currentState && context === currentCtx && Date.now() - lastHwSet < COOLDOWN_MS) return;
- if (state === 'done') {
- setDisplay(context, 'done');
- currentState = 'done';
- setTimeout(function () {
- if (currentState === 'done') { currentState = 'idle'; setDisplay(context, 'idle'); }
- }, DONE_CUE_MS);
- } else if (state === 'recovered') {
- currentState = 'idle'; setDisplay(context, 'idle');
- } else {
- currentState = state; setDisplay(context, state);
+ if (state === currentState && context === currentCtx && Date.now() - lastHwSet < COOLDOWN_MS) return;
+ if (state === 'done') {
+ setDisplay(context, 'done'); currentState = 'done';
+ setTimeout(function() { if (currentState === 'done') { currentState = 'idle'; setDisplay(context, 'idle'); } }, DONE_CUE_MS);
+ } else if (state === 'recovered') { currentState = 'idle'; setDisplay(context, 'idle'); }
+ else { currentState = state; setDisplay(context, state); }
+ lastHwSet = Date.now(); dlog('State: ' + state + ' [' + (currentAgent ? currentAgent.label : '?') + ']');
+}
+
+function findFiles(rootDir, suffix, maxDepth) {
+ var results = []; maxDepth = maxDepth || 16;
+ if (!fs.existsSync(rootDir)) return results;
+ (function walk(dir, depth) {
+ if (depth > maxDepth) 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);
}
- lastHwSet = Date.now();
- dlog('State: ' + state);
+ })(rootDir, 0);
+ return results;
}
-// ─── Session JSONL 解析 ────────────────────────────────
-function normalizeEvent(obj) {
- var type = obj.type || '';
- var pt = (obj.payload || {}).type || '';
- var it = ((obj.payload || {}).item || {}).type || '';
- if (type === '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;
+function latestFiles(rootDir, suffix, limit) {
+ try {
+ var files = findFiles(rootDir, suffix);
+ files.sort(function(a,b) { return fs.statSync(b).mtimeMs - fs.statSync(a).mtimeMs; });
+ return files.slice(0, limit || 3);
+ } catch (_) { return []; }
+}
+
+function tailFile(filePath, agent, context) {
+ 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 = STATE_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 = STATE_PRIORITY.indexOf(s); if (pri >= 0 && pri < bestPri) { bestPri = pri; bestState = s; } }
+ } catch (_) {}
}
- if (type === '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;
+ if (bestState) { dlog('Detected: ' + bestState + ' from ' + path.basename(filePath)); setState(context, bestState); detected = true; }
+ } catch (_) { delete sessionOffsets[filePath]; }
+ return detected;
}
-// ─── Session tails ─────────────────────────────────────
-function latestSessionFiles() {
- try {
- var dir = path.join(HOME, '.codex', 'sessions');
- var files = findFiles(dir, '.jsonl');
- files.sort(function (a, b) { return fs.statSync(b).mtimeMs - fs.statSync(a).mtimeMs; });
- return files.slice(0, 3);
- } catch (_) { return []; }
-}
-
-function tailSession(filePath, context) {
- 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 = STATE_PRIORITY.length, bestState = null;
- for (var i = 0; i < lines.length; i++) {
- try {
- var obj = JSON.parse(lines[i]);
- var s = normalizeEvent(obj);
- if (s) {
- var pri = STATE_PRIORITY.indexOf(s);
- if (pri >= 0 && pri < bestPri) { bestPri = pri; bestState = s; }
- }
- } catch (_) {}
- }
- if (bestState) {
- dlog('Detected: ' + bestState + ' from ' + path.basename(filePath));
- setState(context, bestState);
- detected = true;
- }
- } catch (_) { delete sessionOffsets[filePath]; }
- return detected;
-}
-
-// ─── Health monitoring ─────────────────────────────────
-var healthState = { reconnecting: false };
-
-function parseHealthLine(line) {
- if (line.indexOf(HEALTH_MARKER) === -1) return null;
- var fieldRe = /([A-Za-z][A-Za-z0-9_]*)=("[^"]*"|\S+)/g;
- var fields = {}, m;
- while ((m = fieldRe.exec(line)) !== null) {
- var val = m[2];
- if (val.charAt(0) === '"' && val.charAt(val.length - 1) === '"') val = val.slice(1, -1);
- fields[m[1]] = val;
- }
- var next = fields.next || '';
- var prev = fields.previous || '';
- var attempt = parseInt(fields.reconnectAttempt || '0', 10);
- var scheduled = (fields.reconnectTimerScheduled || '').toLowerCase() === 'true';
- var err = fields.connectionError || '';
- var cause = fields.cause || '';
- if (next === 'connected' && healthState.reconnecting) { healthState.reconnecting = false; return 'recovered'; }
- if ((next === 'connecting' || next === 'disconnected') && (attempt > 0 || scheduled || (err && err.toLowerCase() !== 'null') || (prev === 'connected' && cause !== 'stop_process' && cause !== 'shutdown'))) {
- healthState.reconnecting = true; return 'reconnecting';
- }
- return null;
-}
-
-function latestHealthLogs() {
- try {
- var dir = process.platform === 'win32' ? path.join(HOME, 'AppData', 'Local', 'Codex', 'Logs') : path.join(HOME, 'Library', 'Logs', 'com.openai.codex');
- var files = findFiles(dir, '.log');
- files.sort(function (a, b) { return fs.statSync(b).mtimeMs - fs.statSync(a).mtimeMs; });
- return files.slice(0, 3);
- } catch (_) { return []; }
-}
-
-function tailHealth(filePath, context) {
- try {
- var stat = fs.statSync(filePath);
- var off = healthOffsets[filePath] || 0;
- if (stat.size < off) { healthOffsets[filePath] = 0; return; }
- if (stat.size === off) return;
- 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);
- healthOffsets[filePath] = stat.size;
- var lines = buf.toString('utf-8').split('\n').filter(function (l) { return l.trim(); });
- var lastHealth = null;
- for (var i = 0; i < lines.length; i++) {
- var s = parseHealthLine(lines[i]);
- if (s) lastHealth = s;
- }
- if (lastHealth) { dlog('Health: ' + lastHealth); setState(context, lastHealth); }
- } catch (_) { delete healthOffsets[filePath]; }
-}
-
-// ─── 主轮询 ────────────────────────────────────────────
var pollCount = 0;
-function poll(context, interval, showHealth) {
- pollCount++;
- if (pollCount % 10 === 1) dlog('Poll #' + pollCount);
-
- var detected = false;
- var files = latestSessionFiles();
- for (var i = 0; i < files.length; i++) {
- if (tailSession(files[i], context)) detected = true;
- }
-
- if (detected) lastActivityTime = Date.now();
-
- // Idle timeout: return to IDLE if nothing happened for IDLE_TIMEOUT_MS
- if (currentState !== 'idle' && currentState !== 'done' && Date.now() - lastActivityTime > IDLE_TIMEOUT_MS) {
- dlog('Idle timeout - returning to IDLE');
- setState(context, 'idle');
- }
-
- if (showHealth) {
- var hfiles = latestHealthLogs();
- for (var j = 0; j < hfiles.length; j++) tailHealth(hfiles[j], context);
- }
+function poll(context, interval, agent) {
+ pollCount++;
+ if (pollCount % 10 === 1) dlog('Poll #' + pollCount + ' [' + (agent ? agent.label : '?') + ']');
+ if (!agent) return;
+ var detected = false;
+ var files = latestFiles(agent.scanDir, agent.fileSuffix);
+ for (var i = 0; i < files.length; i++) { if (tailFile(files[i], agent, context)) detected = true; }
+ if (detected) lastActivityTime = Date.now();
+ if (currentState !== 'idle' && currentState !== 'done' && Date.now() - lastActivityTime > IDLE_TIMEOUT_MS) {
+ dlog('Idle timeout - returning to IDLE'); setState(context, 'idle');
+ }
}
-// ─── Action 定义 ───────────────────────────────────────
plugin.monitor = new Actions({
- default: { pollInterval: DEFAULT_POLL, showHealth: true },
-
- _willAppear: function (data) {
- var ctx = data.context;
- dlog('WillAppear: ' + ctx);
- currentCtx = ctx;
- currentState = 'idle';
- lastHwSet = 0;
- lastActivityTime = Date.now();
- setDisplay(ctx, 'idle');
-
- var s = plugin.monitor.data[ctx];
- var interval = s.pollInterval || DEFAULT_POLL;
- var showHealth = s.showHealth !== false;
- poll(ctx, interval, showHealth);
- timers[ctx] = setInterval(function () { poll(ctx, interval, showHealth); }, interval);
- if (!animationHandle) animationHandle = setInterval(animateTick, 200);
- },
-
- _willDisappear: function (data) {
- var ctx = data.context;
- dlog('WillDisappear: ' + ctx);
- 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; }
- },
-
- _didReceiveSettings: function (data) {
- var ctx = data.context;
- if (timers[ctx]) clearInterval(timers[ctx]);
- var s = plugin.monitor.data[ctx];
- var interval = s.pollInterval || DEFAULT_POLL;
- var showHealth = s.showHealth !== false;
- poll(ctx, interval, showHealth);
- timers[ctx] = setInterval(function () { poll(ctx, interval, showHealth); }, interval);
- },
-
- keyUp: function (data) {
- var s = plugin.monitor.data[data.context];
- poll(data.context, s.pollInterval || DEFAULT_POLL, s.showHealth !== false);
- },
-
- sendToPlugin: function (data) {
- if (data.payload && data.payload.command === 'refresh') {
- var s = plugin.monitor.data[data.context];
- poll(data.context, s.pollInterval || DEFAULT_POLL, s.showHealth !== false);
- }
+ default: { pollInterval: DEFAULT_POLL, agent: 'codex' },
+ _willAppear: function(data) {
+ var ctx = data.context, s = plugin.monitor.data[ctx], agentKey = s.agent || 'codex', agent = AGENTS[agentKey];
+ if (!agent) { agent = AGENTS['codex']; dlog('Unknown agent: ' + agentKey + ', falling back to codex'); }
+ currentAgent = agent; dlog('WillAppear: ' + ctx + ' agent=' + agentKey);
+ currentCtx = ctx; currentState = 'idle'; lastHwSet = 0; lastActivityTime = Date.now();
+ setDisplay(ctx, 'idle');
+ var interval = s.pollInterval || DEFAULT_POLL;
+ poll(ctx, interval, agent);
+ timers[ctx] = setInterval(function() { poll(ctx, interval, agent); }, 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; currentAgent = null; currentState = 'idle'; }
+ if (Object.keys(timers).length === 0 && animationHandle) { clearInterval(animationHandle); animationHandle = null; }
+ },
+ _didReceiveSettings: function(data) {
+ var ctx = data.context;
+ if (timers[ctx]) clearInterval(timers[ctx]);
+ var s = plugin.monitor.data[ctx], agentKey = s.agent || 'codex', agent = AGENTS[agentKey];
+ if (!agent) agent = AGENTS['codex']; currentAgent = agent;
+ var interval = s.pollInterval || DEFAULT_POLL;
+ poll(ctx, interval, agent);
+ timers[ctx] = setInterval(function() { poll(ctx, interval, agent); }, interval);
+ },
+ keyUp: function(data) {
+ var s = plugin.monitor.data[data.context], agent = AGENTS[s.agent || 'codex'] || AGENTS['codex'];
+ poll(data.context, s.pollInterval || DEFAULT_POLL, agent);
+ },
+ sendToPlugin: function(data) {
+ if (data.payload && data.payload.command === 'refresh') {
+ var s = plugin.monitor.data[data.context], agent = AGENTS[s.agent || 'codex'] || AGENTS['codex'];
+ poll(data.context, s.pollInterval || DEFAULT_POLL, agent);
}
-});
+ }
+});
\ No newline at end of file
diff --git a/com.streamdock.codex.monitor.sdPlugin/propertyInspector/codex-monitor/index.html b/com.streamdock.codex.monitor.sdPlugin/propertyInspector/codex-monitor/index.html
index d5a395e..3e59c1d 100644
--- a/com.streamdock.codex.monitor.sdPlugin/propertyInspector/codex-monitor/index.html
+++ b/com.streamdock.codex.monitor.sdPlugin/propertyInspector/codex-monitor/index.html
@@ -105,6 +105,14 @@
+
+
+
+
diff --git a/com.streamdock.codex.monitor.sdPlugin/propertyInspector/codex-monitor/index.js b/com.streamdock.codex.monitor.sdPlugin/propertyInspector/codex-monitor/index.js
index dbf5bf8..7d602c2 100644
--- a/com.streamdock.codex.monitor.sdPlugin/propertyInspector/codex-monitor/index.js
+++ b/com.streamdock.codex.monitor.sdPlugin/propertyInspector/codex-monitor/index.js
@@ -1,6 +1,7 @@
(function () {
'use strict';
+ const agentSelect = document.getElementById('agentSelect');
const pollSlider = document.getElementById('pollInterval');
const pollVal = document.getElementById('pollVal');
const showHealthToggle = document.getElementById('showHealth');
@@ -11,6 +12,7 @@
$settings = $settings || {};
function applySettings(settings) {
+ if (settings.agent) { agentSelect.value = settings.agent; }
if (settings.pollInterval !== undefined) {
pollSlider.value = settings.pollInterval;
pollVal.textContent = settings.pollInterval + 'ms';
@@ -20,6 +22,10 @@
}
}
+ agentSelect.addEventListener('change', function () {
+ $settings.agent = this.value;
+ });
+
pollSlider.addEventListener('input', function () {
const val = parseInt(this.value, 10);
pollVal.textContent = val + 'ms';