agent-window 1.2.1 → 1.2.4

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.
@@ -0,0 +1,12 @@
1
+ {
2
+ "BOT_TOKEN": "MTQ2MjczODE4MDc4NzY2Njk3Nw.GZYfmO.z1yzHmhHZbbg0sy-oJDDyNxSpedsXVI1BpifOY",
3
+ "CLAUDE_CODE_OAUTH_TOKEN": "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",
4
+ "PROJECT_DIR": "/Users/cyrus/Documents/project/cognitive-prototype/interactive-prototype-cognitiveOS",
5
+ "ALLOWED_CHANNELS": "1462723721188868096",
6
+ "_comment_workspace": "工作空间配置(v1.0.9+ 新标准)",
7
+ "workspace": {
8
+ "containerName": "claude-sandbox",
9
+ "dockerImage": "claude-sandbox:latest",
10
+ "portMappings": []
11
+ }
12
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-window",
3
- "version": "1.2.1",
3
+ "version": "1.2.4",
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",
@@ -24,6 +24,7 @@ import {
24
24
  } from '../../core/instance/pm2-bridge.js';
25
25
  import { readConfig, writeConfig } from '../../core/instance/config-reader.js';
26
26
  import { existsSync } from 'fs';
27
+ import path from 'path';
27
28
 
28
29
  /**
29
30
  * Register instance routes
@@ -100,36 +101,84 @@ export async function registerInstanceRoutes(fastify) {
100
101
  projectPath: { type: 'string' },
101
102
  displayName: { type: 'string' },
102
103
  tags: { type: 'array', items: { type: 'string' } },
103
- configPath: { type: 'string' }
104
+ configPath: { type: 'string' },
105
+ config: { type: 'object' },
106
+ createConfig: { type: 'boolean' }
104
107
  }
105
108
  }
106
109
  }
107
110
  }, async (request, reply) => {
108
111
  try {
109
- const { name, projectPath, displayName, tags, configPath } = request.body;
112
+ const { name, projectPath, displayName, tags, configPath, config, createConfig } = request.body;
113
+
114
+ // If creating config file, ensure project path exists or create it
115
+ if (createConfig) {
116
+ const fs = await import('fs/promises');
117
+ const { existsSync } = await import('fs');
118
+
119
+ // Create project directory if it doesn't exist
120
+ if (!existsSync(projectPath)) {
121
+ try {
122
+ await fs.mkdir(projectPath, { recursive: true });
123
+ } catch (err) {
124
+ return reply.code(400).send({
125
+ error: 'Failed to create project directory',
126
+ message: err.message
127
+ });
128
+ }
129
+ }
110
130
 
111
- // Validate project path exists
112
- if (!existsSync(projectPath)) {
113
- return reply.code(400).send({
114
- error: 'Project path does not exist',
115
- path: projectPath
131
+ // Write config file
132
+ const configFilePath = configPath || path.join(projectPath, 'config.json');
133
+ try {
134
+ await fs.writeFile(configFilePath, JSON.stringify(config, null, 2), 'utf-8');
135
+ } catch (err) {
136
+ return reply.code(400).send({
137
+ error: 'Failed to write config file',
138
+ message: err.message
139
+ });
140
+ }
141
+
142
+ // Use the created config path
143
+ const finalConfigPath = configFilePath;
144
+ const result = await addInstance(name, projectPath, {
145
+ displayName,
146
+ tags,
147
+ configPath: finalConfigPath
116
148
  });
117
- }
118
149
 
119
- const result = await addInstance(name, projectPath, {
120
- displayName,
121
- tags,
122
- configPath
123
- });
150
+ if (!result.success) {
151
+ return reply.code(400).send({
152
+ error: result.error,
153
+ validation: result.validation
154
+ });
155
+ }
124
156
 
125
- if (!result.success) {
126
- return reply.code(400).send({
127
- error: result.error,
128
- validation: result.validation
157
+ reply.code(201).send(result.instance);
158
+ } else {
159
+ // Validate project path exists for non-config creation
160
+ if (!existsSync(projectPath)) {
161
+ return reply.code(400).send({
162
+ error: 'Project path does not exist',
163
+ path: projectPath
164
+ });
165
+ }
166
+
167
+ const result = await addInstance(name, projectPath, {
168
+ displayName,
169
+ tags,
170
+ configPath
129
171
  });
130
- }
131
172
 
132
- reply.code(201).send(result.instance);
173
+ if (!result.success) {
174
+ return reply.code(400).send({
175
+ error: result.error,
176
+ validation: result.validation
177
+ });
178
+ }
179
+
180
+ reply.code(201).send(result.instance);
181
+ }
133
182
  } catch (error) {
134
183
  reply.code(500).send({
135
184
  error: 'Failed to add instance',
@@ -411,5 +460,57 @@ export async function registerInstanceRoutes(fastify) {
411
460
  }
412
461
  });
413
462
 
463
+ /**
464
+ * GET /api/instances/tokens/claude
465
+ * Get Claude OAuth tokens from existing instances
466
+ */
467
+ fastify.get('/api/instances/tokens/claude', {
468
+ schema: {
469
+ description: 'Get Claude tokens from existing instances for reuse'
470
+ }
471
+ }, async (request, reply) => {
472
+ try {
473
+ const instances = await listInstances();
474
+ const tokens = [];
475
+
476
+ for (const instance of instances) {
477
+ try {
478
+ const result = await readConfig(instance.configPath);
479
+ if (!result.success) continue;
480
+
481
+ const config = JSON.parse(result.config);
482
+ if (config.CLAUDE_CODE_OAUTH_TOKEN) {
483
+ // Mask the token for security (show first 12 and last 4 characters)
484
+ const token = config.CLAUDE_CODE_OAUTH_TOKEN;
485
+ const masked = token.length > 16
486
+ ? `${token.substring(0, 12)}...${token.substring(token.length - 4)}`
487
+ : token;
488
+
489
+ tokens.push({
490
+ instanceName: instance.name,
491
+ displayName: instance.displayName || instance.name,
492
+ token: token, // Full token for reuse
493
+ maskedToken: masked, // Display version
494
+ instanceType: instance.instanceType
495
+ });
496
+ }
497
+ } catch (err) {
498
+ // Skip instances that can't be read
499
+ continue;
500
+ }
501
+ }
502
+
503
+ return {
504
+ success: true,
505
+ tokens
506
+ };
507
+ } catch (error) {
508
+ reply.code(500).send({
509
+ error: 'Failed to get tokens',
510
+ message: error.message
511
+ });
512
+ }
513
+ });
514
+
414
515
  fastify.log.info('Instance routes registered');
415
516
  }
@@ -19,7 +19,7 @@ import { getInstance } from '../../core/instance/manager.js';
19
19
 
20
20
  // Get package root directory
21
21
  const __dirname = dirname(fileURLToPath(import.meta.url));
22
- const PACKAGE_ROOT = path.join(__dirname, '..', '..');
22
+ const PACKAGE_ROOT = path.join(__dirname, '..', '..', '..');
23
23
 
24
24
  /**
25
25
  * Register operation routes
@@ -183,5 +183,225 @@ export async function registerOperationRoutes(fastify) {
183
183
  }
184
184
  });
185
185
 
186
+ /**
187
+ * POST /api/validate-config
188
+ * Validate configuration before creating instance
189
+ */
190
+ fastify.post('/api/validate-config', {
191
+ schema: {
192
+ description: 'Validate instance configuration',
193
+ body: {
194
+ type: 'object',
195
+ required: ['config'],
196
+ properties: {
197
+ config: { type: 'object' },
198
+ projectPath: { type: 'string' }
199
+ }
200
+ }
201
+ }
202
+ }, async (request, reply) => {
203
+ try {
204
+ const { config, projectPath } = request.body;
205
+ const { existsSync } = await import('fs');
206
+ const { promisify } = await import('util');
207
+ const { exec } = await import('child_process');
208
+ const execAsync = promisify(exec);
209
+
210
+ const checks = [];
211
+ let allPassed = true;
212
+
213
+ // Check 1: Required fields
214
+ const requiredFields = ['BOT_TOKEN', 'CLAUDE_CODE_OAUTH_TOKEN', 'PROJECT_DIR', 'ALLOWED_CHANNELS'];
215
+ const missingFields = requiredFields.filter(field => !config[field]);
216
+
217
+ if (missingFields.length > 0) {
218
+ checks.push({
219
+ name: 'Required Fields',
220
+ status: 'failed',
221
+ message: `Missing fields: ${missingFields.join(', ')}`
222
+ });
223
+ allPassed = false;
224
+ } else {
225
+ checks.push({
226
+ name: 'Required Fields',
227
+ status: 'passed',
228
+ message: 'All required fields present'
229
+ });
230
+ }
231
+
232
+ // Check 2: Discord token format
233
+ if (config.BOT_TOKEN) {
234
+ if (config.BOT_TOKEN.startsWith('MT') || config.BOT_TOKEN.length >= 50) {
235
+ checks.push({
236
+ name: 'Discord Token Format',
237
+ status: 'passed',
238
+ message: 'Token format looks valid'
239
+ });
240
+ } else {
241
+ checks.push({
242
+ name: 'Discord Token Format',
243
+ status: 'warning',
244
+ message: 'Token format may be invalid (should start with MT and be long)'
245
+ });
246
+ }
247
+ }
248
+
249
+ // Check 3: Claude OAuth token format
250
+ if (config.CLAUDE_CODE_OAUTH_TOKEN) {
251
+ if (config.CLAUDE_CODE_OAUTH_TOKEN.startsWith('sk-ant-')) {
252
+ checks.push({
253
+ name: 'Claude OAuth Token Format',
254
+ status: 'passed',
255
+ message: 'Token format valid'
256
+ });
257
+ } else {
258
+ checks.push({
259
+ name: 'Claude OAuth Token Format',
260
+ status: 'failed',
261
+ message: 'Token must start with sk-ant-'
262
+ });
263
+ allPassed = false;
264
+ }
265
+ }
266
+
267
+ // Check 4: Project directory exists or can be created
268
+ if (config.PROJECT_DIR) {
269
+ if (existsSync(config.PROJECT_DIR)) {
270
+ checks.push({
271
+ name: 'Project Directory',
272
+ status: 'passed',
273
+ message: `Directory exists: ${config.PROJECT_DIR}`
274
+ });
275
+ } else if (projectPath) {
276
+ // Will be created
277
+ checks.push({
278
+ name: 'Project Directory',
279
+ status: 'warning',
280
+ message: `Will be created: ${config.PROJECT_DIR}`
281
+ });
282
+ } else {
283
+ checks.push({
284
+ name: 'Project Directory',
285
+ status: 'warning',
286
+ message: `Directory does not exist: ${config.PROJECT_DIR}`
287
+ });
288
+ }
289
+ }
290
+
291
+ // Check 5: Allowed channels format
292
+ if (config.ALLOWED_CHANNELS) {
293
+ const channels = config.ALLOWED_CHANNELS.split(',').map(c => c.trim()).filter(Boolean);
294
+ const validChannels = channels.every(c => /^\d+$/.test(c));
295
+
296
+ if (validChannels && channels.length > 0) {
297
+ checks.push({
298
+ name: 'Allowed Channels',
299
+ status: 'passed',
300
+ message: `${channels.length} channel(s) specified`
301
+ });
302
+ } else {
303
+ checks.push({
304
+ name: 'Allowed Channels',
305
+ status: 'warning',
306
+ message: 'Channel IDs should be numeric, comma-separated'
307
+ });
308
+ }
309
+ }
310
+
311
+ // Check 6: PM2 availability
312
+ try {
313
+ await execAsync('which pm2');
314
+ checks.push({
315
+ name: 'PM2 Installation',
316
+ status: 'passed',
317
+ message: 'PM2 is installed'
318
+ });
319
+ } catch {
320
+ checks.push({
321
+ name: 'PM2 Installation',
322
+ status: 'failed',
323
+ message: 'PM2 not found. Install: npm install -g pm2'
324
+ });
325
+ allPassed = false;
326
+ }
327
+
328
+ // Check 7: Docker availability AND functionality
329
+ try {
330
+ await execAsync('which docker');
331
+
332
+ // Actually test if Docker daemon is running
333
+ try {
334
+ const { stdout } = await execAsync('docker info');
335
+ checks.push({
336
+ name: 'Docker Installation',
337
+ status: 'passed',
338
+ message: 'Docker is installed and running'
339
+ });
340
+ } catch (dockerErr) {
341
+ checks.push({
342
+ name: 'Docker Installation',
343
+ status: 'warning',
344
+ message: 'Docker installed but daemon not running. Start Docker Desktop.'
345
+ });
346
+ }
347
+ } catch {
348
+ checks.push({
349
+ name: 'Docker Installation',
350
+ status: 'warning',
351
+ message: 'Docker not found (required for sandbox feature)'
352
+ });
353
+ }
354
+
355
+ // Check 8: Discord Token validation (basic format check)
356
+ if (config.BOT_TOKEN) {
357
+ // Discord tokens start with specific prefixes and are base64-like
358
+ const tokenValid = config.BOT_TOKEN.length >= 50 &&
359
+ (config.BOT_TOKEN.startsWith('MT') ||
360
+ config.BOT_TOKEN.includes('.') ||
361
+ /^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/.test(config.BOT_TOKEN));
362
+
363
+ if (tokenValid) {
364
+ checks.push({
365
+ name: 'Discord Token Validation',
366
+ status: 'passed',
367
+ message: 'Token format appears valid'
368
+ });
369
+ } else {
370
+ checks.push({
371
+ name: 'Discord Token Validation',
372
+ status: 'warning',
373
+ message: 'Token format may be invalid (should be 50+ chars with dots)'
374
+ });
375
+ }
376
+ }
377
+
378
+ // Check 9: AgentWindow bot.js exists
379
+ if (existsSync(path.join(PACKAGE_ROOT, 'src', 'bot.js'))) {
380
+ checks.push({
381
+ name: 'AgentWindow Installation',
382
+ status: 'passed',
383
+ message: 'AgentWindow bot.js found'
384
+ });
385
+ } else {
386
+ checks.push({
387
+ name: 'AgentWindow Installation',
388
+ status: 'failed',
389
+ message: `AgentWindow bot.js not found at ${path.join(PACKAGE_ROOT, 'src', 'bot.js')}`
390
+ });
391
+ allPassed = false;
392
+ }
393
+
394
+ return {
395
+ success: allPassed,
396
+ checks
397
+ };
398
+ } catch (error) {
399
+ reply.code(500).send({
400
+ error: 'Validation failed',
401
+ message: error.message
402
+ });
403
+ }
404
+ });
405
+
186
406
  fastify.log.info('Operation routes registered');
187
407
  }