agent-window 1.2.7 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-window",
3
- "version": "1.2.7",
3
+ "version": "1.3.0",
4
4
  "description": "A window to interact with AI agents through chat interfaces. Simplified interaction, powerful backend capabilities.",
5
5
  "type": "module",
6
6
  "main": "src/bot.js",
@@ -244,7 +244,7 @@ export async function registerInstanceRoutes(fastify) {
244
244
 
245
245
  // Use botName for PM2 lookup, default to bot-{name}
246
246
  const botName = instance.botName || `bot-${name}`;
247
- const status = await getStatus(botName);
247
+ const status = await getStatus(botName, { instanceType: instance.instanceType });
248
248
  return { ...status, instanceName: name };
249
249
  } catch (error) {
250
250
  reply.code(500).send({
@@ -21,6 +21,37 @@ import { getInstance } from '../../core/instance/manager.js';
21
21
  const __dirname = dirname(fileURLToPath(import.meta.url));
22
22
  const PACKAGE_ROOT = path.join(__dirname, '..', '..', '..');
23
23
 
24
+ /**
25
+ * Normalize instance type to handle legacy values
26
+ * Maps old/invalid types to current supported types
27
+ *
28
+ * @param {string} type - Raw instance type
29
+ * @returns {'bmad-plugin' | 'simple-config'} Normalized type
30
+ */
31
+ function normalizeInstanceType(type) {
32
+ if (!type) return 'simple-config';
33
+
34
+ const typeMap = {
35
+ 'standalone': 'simple-config', // Legacy: convert to simple-config
36
+ 'bmad': 'bmad-plugin', // Legacy: shorthand
37
+ 'simple': 'simple-config', // Legacy: shorthand
38
+ 'unknown': 'simple-config', // Fallback for unknown types
39
+ };
40
+
41
+ // If exact match, return mapped value
42
+ if (typeMap[type] !== undefined) {
43
+ return typeMap[type];
44
+ }
45
+
46
+ // If already a valid type, return as-is
47
+ if (type === 'bmad-plugin' || type === 'simple-config') {
48
+ return type;
49
+ }
50
+
51
+ // Safe default for any unexpected value
52
+ return 'simple-config';
53
+ }
54
+
24
55
  /**
25
56
  * Register operation routes
26
57
  */
@@ -49,28 +80,23 @@ export async function registerOperationRoutes(fastify) {
49
80
  });
50
81
  }
51
82
 
52
- // Determine script path based on instance type
83
+ // Normalize instance type to handle legacy values
84
+ const normalizedType = normalizeInstanceType(instance.instanceType);
85
+
86
+ // Determine script path based on normalized instance type
53
87
  let scriptPath;
54
88
  const cwd = instance.projectPath;
55
89
 
56
- switch (instance.instanceType) {
90
+ switch (normalizedType) {
57
91
  case 'bmad-plugin':
58
92
  // BMAD plugin: start _agent-bridge/src/bot.js
59
93
  scriptPath = path.join(instance.projectPath, '_agent-bridge', 'src', 'bot.js');
60
94
  break;
61
- case 'standalone':
62
- // Standalone: start src/bot.js from project root
63
- scriptPath = path.join(instance.projectPath, 'src', 'bot.js');
64
- break;
65
95
  case 'simple-config':
66
- // Simple config: use global agent-window bot.js
96
+ default:
97
+ // Simple config: use global agent-window bot.js (default fallback)
67
98
  scriptPath = path.join(PACKAGE_ROOT, 'src', 'bot.js');
68
99
  break;
69
- default:
70
- return reply.code(400).send({
71
- error: 'Unknown instance type',
72
- instanceType: instance.instanceType
73
- });
74
100
  }
75
101
 
76
102
  const botName = instance.botName || `bot-${name}`;
@@ -144,6 +144,9 @@ export async function addInstance(name, projectPath, options = {}) {
144
144
  pluginPath: null,
145
145
  botName: `bot-${name}`,
146
146
  instanceType: 'simple-config',
147
+ // Extensible fields for future platform/AI provider support
148
+ platform: options.platform || 'discord', // discord | telegram | slack
149
+ aiProvider: options.aiProvider || null, // anthropic | openai | microsoft
147
150
  addedAt: new Date().toISOString(),
148
151
  tags: options.tags || [],
149
152
  enabled: true
@@ -165,9 +168,7 @@ export async function addInstance(name, projectPath, options = {}) {
165
168
  // Only BMAD plugins can be manually added
166
169
  const typeMsg = validation.instanceType === 'simple-config'
167
170
  ? '简单配置实例无法通过"添加实例"功能添加。请使用"Discover"功能导入。'
168
- : validation.instanceType === 'unknown'
169
- ? '无法识别的项目类型,必须是有效的 BMAD 插件'
170
- : '项目不是有效的 AgentWindow BMAD 插件';
171
+ : '项目不是有效的 BMAD 插件';
171
172
 
172
173
  return {
173
174
  success: false,
@@ -196,6 +197,9 @@ export async function addInstance(name, projectPath, options = {}) {
196
197
  configPath,
197
198
  botName: `bot-${name}`,
198
199
  instanceType: validation.instanceType,
200
+ // Extensible fields for future platform/AI provider support
201
+ platform: options.platform || 'discord', // discord | telegram | slack
202
+ aiProvider: options.aiProvider || null, // anthropic | openai | microsoft
199
203
  addedAt: new Date().toISOString(),
200
204
  tags: options.tags || [],
201
205
  enabled: true
@@ -285,7 +289,7 @@ export async function updateInstance(name, updates) {
285
289
  }
286
290
 
287
291
  // Allowed fields to update
288
- const allowedFields = ['displayName', 'tags', 'enabled', 'configPath'];
292
+ const allowedFields = ['displayName', 'tags', 'enabled', 'configPath', 'platform', 'aiProvider'];
289
293
  const instance = data.instances[index];
290
294
 
291
295
  for (const [key, value] of Object.entries(updates)) {
@@ -155,9 +155,12 @@ export async function getLogs(name, options = {}) {
155
155
  /**
156
156
  * Get process status summary
157
157
  * @param {string} name - Process name
158
+ * @param {Object} options - Options
159
+ * @param {string} options.instanceType - Instance type for type-aware checks
158
160
  * @returns {Promise<Object>} Status info
159
161
  */
160
- export async function getStatus(name) {
162
+ export async function getStatus(name, options = {}) {
163
+ const { instanceType } = options;
161
164
  const proc = await getProcess(name);
162
165
 
163
166
  if (!proc) {
@@ -198,54 +201,64 @@ export async function getStatus(name) {
198
201
  console.debug('[Status] Could not read config for container name:', e.message);
199
202
  }
200
203
 
201
- // Check Docker container status (always check, don't rely on stale health file)
204
+ // Type-aware status detection
202
205
  let dockerRunning = false;
203
- if (containerName && procStatus === 'online') {
204
- try {
205
- const { execSync } = await import('child_process');
206
- const result = execSync(
207
- `docker inspect -f '{{.State.Running}}' ${containerName} 2>/dev/null`,
208
- { encoding: 'utf-8', timeout: 5000 }
209
- ).trim();
210
- dockerRunning = result === 'true';
211
- } catch (e) {
212
- // Docker container not running or doesn't exist
213
- dockerRunning = false;
206
+
207
+ // For bmad-plugin: Only check PM2 status (BMAD plugin manages its own Docker)
208
+ if (instanceType === 'bmad-plugin') {
209
+ healthStatus.docker = false; // Not tracked by AgentWindow for BMAD plugins
210
+ healthStatus.pm2 = procStatus === 'online';
211
+
212
+ // BMAD plugin status depends only on PM2
213
+ if (procStatus === 'online') {
214
+ healthStatus.overall = 'healthy';
215
+ } else if (procStatus === 'errored') {
216
+ healthStatus.overall = 'error';
217
+ } else {
218
+ healthStatus.overall = 'stopped';
214
219
  }
215
220
  }
221
+ // For simple-config: Check both PM2 and Docker status
222
+ else {
223
+ // simple-config instances may use Docker sandbox
224
+ if (containerName && procStatus === 'online') {
225
+ try {
226
+ const { execSync } = await import('child_process');
227
+ const result = execSync(
228
+ `docker inspect -f '{{.State.Running}}' ${containerName} 2>/dev/null`,
229
+ { encoding: 'utf-8', timeout: 5000 }
230
+ ).trim();
231
+ dockerRunning = result === 'true';
232
+ } catch (e) {
233
+ dockerRunning = false;
234
+ }
235
+ }
216
236
 
217
- healthStatus.docker = dockerRunning;
218
- healthStatus.pm2 = procStatus === 'online';
237
+ healthStatus.docker = dockerRunning;
238
+ healthStatus.pm2 = procStatus === 'online';
219
239
 
220
- // Determine overall status based on PM2 + Docker combination
221
- if (procStatus === 'stopped') {
222
- healthStatus.overall = 'stopped';
223
- } else if (procStatus === 'errored' || procStatus === 'stopped') {
224
- healthStatus.overall = 'error';
225
- } else if (procStatus === 'online') {
226
- if (dockerRunning) {
227
- healthStatus.overall = 'healthy';
228
- } else if (containerName) {
229
- // Has container name but container not running = degraded
230
- healthStatus.overall = 'degraded';
240
+ // Determine overall status based on PM2 + Docker combination
241
+ if (procStatus === 'stopped') {
242
+ healthStatus.overall = 'stopped';
243
+ } else if (procStatus === 'errored') {
244
+ healthStatus.overall = 'error';
245
+ } else if (procStatus === 'online') {
246
+ if (dockerRunning) {
247
+ healthStatus.overall = 'healthy';
248
+ } else if (containerName) {
249
+ healthStatus.overall = 'degraded'; // Has container but not running
250
+ } else {
251
+ healthStatus.overall = 'healthy'; // No Docker configured
252
+ }
231
253
  } else {
232
- // No container configured (simple instance without Docker)
233
- healthStatus.overall = 'healthy';
254
+ healthStatus.overall = 'unknown';
234
255
  }
235
- } else {
236
- healthStatus.overall = 'unknown';
237
256
  }
238
257
 
239
258
  // Determine display status (what frontend shows)
240
259
  let displayStatus = procStatus;
241
260
  if (procStatus === 'online') {
242
- if (dockerRunning) {
243
- displayStatus = 'running';
244
- } else if (containerName) {
245
- displayStatus = 'degraded'; // PM2 running but Docker down
246
- } else {
247
- displayStatus = 'running'; // No Docker, just PM2
248
- }
261
+ displayStatus = 'running'; // PM2 online = running (Docker state is in health.docker)
249
262
  }
250
263
 
251
264
  return {
@@ -258,7 +271,8 @@ export async function getStatus(name) {
258
271
  memory: proc.memory || 0,
259
272
  cpu: proc.cpu || 0,
260
273
  restarts: proc.restarts || 0,
261
- health: healthStatus
274
+ health: healthStatus,
275
+ instanceType // Include for frontend reference
262
276
  };
263
277
  }
264
278
 
@@ -12,9 +12,18 @@ import path from 'path';
12
12
  import { existsSync } from 'fs';
13
13
 
14
14
  /**
15
- * Detect instance type
15
+ * Detect instance type based on project structure
16
+ *
16
17
  * @param {string} projectPath - Path to check
17
- * @returns {string} 'bmad-plugin' | 'simple-config' | 'unknown'
18
+ * @returns {'bmad-plugin' | 'simple-config'} Instance type (always returns one of two types)
19
+ *
20
+ * Detection Logic:
21
+ * 1. BMAD Plugin: Has _agent-bridge/src/bot.js (full BMAD framework)
22
+ * 2. Simple Config: Any other path (fallback, uses global bot.js)
23
+ *
24
+ * @example
25
+ * detectInstanceType('/path/to/bmad-project') // returns 'bmad-plugin'
26
+ * detectInstanceType('/path/to/simple') // returns 'simple-config'
18
27
  */
19
28
  export function detectInstanceType(projectPath) {
20
29
  const normalizedPath = path.resolve(projectPath);
@@ -28,15 +37,9 @@ export function detectInstanceType(projectPath) {
28
37
  return 'bmad-plugin';
29
38
  }
30
39
 
31
- // Check if it has config.json (simple config instance)
32
- // This takes precedence over _bmad directory, as many BMAD projects
33
- // are run as simple-config bots
34
- const hasConfig = existsSync(path.join(normalizedPath, 'config.json'));
35
- if (hasConfig) {
36
- return 'simple-config';
37
- }
38
-
39
- return 'unknown';
40
+ // Default to simple-config for all other cases
41
+ // This provides a safe fallback that allows the instance to run
42
+ return 'simple-config';
40
43
  }
41
44
 
42
45
  /**
@@ -52,7 +55,7 @@ export function detectInstanceType(projectPath) {
52
55
  * Validation result
53
56
  * @typedef {Object} ValidationResult
54
57
  * @property {boolean} valid - Overall validation result
55
- * @property {string} instanceType - 'bmad' | 'simple' | 'unknown'
58
+ * @property {string} instanceType - 'bmad-plugin' | 'simple-config'
56
59
  * @property {Array<Object>} checks - Individual check results
57
60
  * @property {string|null} pluginPath - Path to the plugin directory
58
61
  * @property {string} projectPath - Path to the project directory
@@ -96,11 +99,12 @@ export async function validateInstance(projectPath) {
96
99
  // Detect instance type
97
100
  const instanceType = detectInstanceType(normalizedPath);
98
101
 
102
+ // Type detection always succeeds (returns bmad-plugin or simple-config)
99
103
  checks.push({
100
104
  name: 'type.detected',
101
- passed: instanceType !== 'unknown',
105
+ passed: true, // Always passes - detectInstanceType always returns a valid type
102
106
  required: true,
103
- error: instanceType === 'unknown' ? '无法识别的实例类型' : `检测到${instanceType === 'bmad-plugin' ? ' BMAD 插件' : '简单配置'}实例`
107
+ error: null // No error - type is always valid
104
108
  });
105
109
 
106
110
  let pluginPath = null;