agent-window 1.2.1 → 1.2.3
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/config/config.json.backup +12 -0
- package/package.json +1 -1
- package/src/api/routes/instances.js +120 -19
- package/src/api/routes/operations.js +221 -1
- package/src/bot.js +200 -2
- package/src/core/instance/manager.js +114 -15
- package/src/core/instance/pm2-bridge.js +56 -6
- package/src/core/instance/validator.js +7 -7
- package/src/core/platform/pm2-bridge.js +15 -0
- package/web/dist/assets/Dashboard-1TTWybTq.js +1 -0
- package/web/dist/assets/Dashboard-DRg2tFpk.css +1 -0
- package/web/dist/assets/{InstanceDetail-B2M2l7Dy.css → InstanceDetail-BRMjUfAV.css} +1 -1
- package/web/dist/assets/InstanceDetail-BWV1wz24.js +3 -0
- package/web/dist/assets/{Instances-VQZar42B.js → Instances-D4SbRoep.js} +1 -1
- package/web/dist/assets/{Settings-BCwW_7gk.js → Settings-CdoSWOhM.js} +1 -1
- package/web/dist/assets/{main-BGxPBmf2.js → main-BKf0mqau.js} +4 -4
- package/web/dist/index.html +1 -1
- package/web/dist/assets/Dashboard-BTrinn8p.js +0 -1
- package/web/dist/assets/Dashboard-BicWOr3D.css +0 -1
- package/web/dist/assets/InstanceDetail-DAERYy59.js +0 -3
|
@@ -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
|
@@ -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
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
150
|
+
if (!result.success) {
|
|
151
|
+
return reply.code(400).send({
|
|
152
|
+
error: result.error,
|
|
153
|
+
validation: result.validation
|
|
154
|
+
});
|
|
155
|
+
}
|
|
124
156
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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
|
-
|
|
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
|
}
|