aws-runtime-bridge 1.6.14 → 1.7.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.
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"terminal.d.ts","sourceRoot":"","sources":["../../src/routes/terminal.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAKxC,OAAO,EAEL,KAAK,oBAAoB,EAEzB,KAAK,aAAa,EAInB,MAAM,qBAAqB,CAAC;AAc7B,eAAO,MAAM,cAAc,4CAAW,CAAC;AAIvC,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,oBAAoB,CAAC;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,GAAG,EAAE,MAAM,CAAC;CACb;AAGD,eAAO,MAAM,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,eAAe,CAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"terminal.d.ts","sourceRoot":"","sources":["../../src/routes/terminal.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAKxC,OAAO,EAEL,KAAK,oBAAoB,EAEzB,KAAK,aAAa,EAInB,MAAM,qBAAqB,CAAC;AAc7B,eAAO,MAAM,cAAc,4CAAW,CAAC;AAIvC,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,oBAAoB,CAAC;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,GAAG,EAAE,MAAM,CAAC;CACb;AAGD,eAAO,MAAM,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,eAAe,CAAa,CAAC;AAgBnE;;GAEG;AACH,wBAAgB,6BAA6B,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAEpE;AAED;;;GAGG;AACH,wBAAgB,6BAA6B,CAC3C,QAAQ,GAAE,MAAM,CAAC,QAA2B,EAC5C,GAAG,GAAE,MAAM,CAAC,UAAwB,GACnC,MAAM,CAMR;AAED;;GAEG;AACH,wBAAgB,2BAA2B,CAAC,QAAQ,SAAkC,GAAG,WAAW,CAMnG;AAED;;GAEG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,WAAW,EAAE,KAAK,CAAC,EAAE,MAAM,EAAE,GAAG,UAAQ,GAAG,MAAM,CAQnG;AAED;;GAEG;AACH,wBAAgB,kCAAkC,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,GAAG,SAAS,CAiB7F;AAiED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,gBAAgB,EAAE,MAAM,GAAG,MAAM,CAErE;AAyGD,wBAAgB,eAAe,CAC7B,OAAO,EAAE,MAAM,EACf,aAAa,EAAE,MAAM,GAAG,SAAS,EACjC,OAAO,EAAE,MAAM,CAAC,UAAU,EAC1B,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACrC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CA+BxB;AA8BD,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,aAAa,GAAG,MAAM,GAAG,SAAS,CAU7E;AAYD,wBAAgB,oBAAoB,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAE7D"}
|
package/dist/routes/terminal.js
CHANGED
|
@@ -17,11 +17,12 @@ import { getAgentProcessManager } from '../services/agent-process-manager.js';
|
|
|
17
17
|
import { enqueueMcpLaunchBinding } from '../services/mcp-launch-binding-queue.js';
|
|
18
18
|
import { findClaudeCodeProcesses } from '../services/process-detector.js';
|
|
19
19
|
import { sendOutput, sendQuestionRequest, sendStatus } from '../services/session-output.js';
|
|
20
|
-
import {
|
|
20
|
+
import { findPersistedSessionByAgentId, removePersistedSession, savePersistedSessions, updatePersistedSessionAutoCommands, upsertPersistedSession, } from '../services/terminal-persistence.js';
|
|
21
21
|
export const terminalRouter = Router();
|
|
22
22
|
// 导出 SDK 会话存储,供其他模块使用(如 sessions.ts)
|
|
23
23
|
export const sdkSessions = new Map();
|
|
24
24
|
const TOOL_RESULT_PREVIEW_MAX_LENGTH = 4000;
|
|
25
|
+
const startingAgents = new Set();
|
|
25
26
|
const terminalCommandStates = new Map();
|
|
26
27
|
const DEFAULT_WINDOWS_TERMINAL_OUTPUT_ENCODING = 'gb18030';
|
|
27
28
|
const DEFAULT_TERMINAL_OUTPUT_ENCODING = 'utf-8';
|
|
@@ -96,6 +97,27 @@ function getTerminalCommandState(entry) {
|
|
|
96
97
|
terminalCommandStates.set(entry.sessionId, state);
|
|
97
98
|
return state;
|
|
98
99
|
}
|
|
100
|
+
function findActiveSdkSessionByAgentId(agentId) {
|
|
101
|
+
for (const entry of sdkSessions.values()) {
|
|
102
|
+
if (entry.agentId === agentId) {
|
|
103
|
+
return entry;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return undefined;
|
|
107
|
+
}
|
|
108
|
+
async function buildExistingSessionResponse(entry) {
|
|
109
|
+
const adapter = adapterRegistry.get(entry.providerId || 'claude-code');
|
|
110
|
+
return {
|
|
111
|
+
sessionId: entry.sessionId,
|
|
112
|
+
status: adapter.getSessionStatus(entry.sessionId) || 'running',
|
|
113
|
+
agentId: entry.agentId,
|
|
114
|
+
workspacePath: entry.workspacePath,
|
|
115
|
+
command: entry.config.command,
|
|
116
|
+
mode: 'sdk',
|
|
117
|
+
providerSessionId: adapter.getProviderSessionId(entry.sessionId),
|
|
118
|
+
reused: true,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
99
121
|
function stopTerminalCommandProcess(sessionId) {
|
|
100
122
|
const state = terminalCommandStates.get(sessionId);
|
|
101
123
|
if (!state?.runningProcess) {
|
|
@@ -374,9 +396,38 @@ async function startSdkSession(req, res) {
|
|
|
374
396
|
// 空闲命令配置
|
|
375
397
|
idleInputAutoCommand, nonInputAutoCommand, } = req.body || {};
|
|
376
398
|
let sessionId = null;
|
|
377
|
-
|
|
399
|
+
const normalizedAgentId = String(agentId || '').trim();
|
|
378
400
|
try {
|
|
379
401
|
ensureAdapterInitialized();
|
|
402
|
+
if (!normalizedAgentId) {
|
|
403
|
+
res.status(400).json({ error: 'agentId is required' });
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
const activeSession = findActiveSdkSessionByAgentId(normalizedAgentId);
|
|
407
|
+
if (activeSession) {
|
|
408
|
+
res.json(await buildExistingSessionResponse(activeSession));
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
const persistedSession = await findPersistedSessionByAgentId(normalizedAgentId);
|
|
412
|
+
if (persistedSession) {
|
|
413
|
+
res.json({
|
|
414
|
+
sessionId: persistedSession.sessionId,
|
|
415
|
+
status: persistedSession.status,
|
|
416
|
+
agentId: persistedSession.agentId,
|
|
417
|
+
workspacePath: persistedSession.workspacePath,
|
|
418
|
+
command: persistedSession.command,
|
|
419
|
+
mode: persistedSession.mode || 'sdk',
|
|
420
|
+
pid: persistedSession.pid,
|
|
421
|
+
reused: true,
|
|
422
|
+
persistedOnly: true,
|
|
423
|
+
});
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
if (startingAgents.has(normalizedAgentId)) {
|
|
427
|
+
res.status(409).json({ error: 'SDK session is already starting for this agent' });
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
startingAgents.add(normalizedAgentId);
|
|
380
431
|
// 根据命令选择对应的 adapter
|
|
381
432
|
const normalizedCommand = (command || 'claude').toLowerCase();
|
|
382
433
|
const providerId = resolveSdkProviderId(command);
|
|
@@ -384,7 +435,6 @@ async function startSdkSession(req, res) {
|
|
|
384
435
|
console.log(`[Runtime] ★★★ SDK启动 ★★★ command="${command}", normalizedCommand="${normalizedCommand}", providerId="${providerId}"`);
|
|
385
436
|
const adapter = adapterRegistry.get(providerId);
|
|
386
437
|
sessionId = uuidv4();
|
|
387
|
-
agentForCleanup = agentId;
|
|
388
438
|
// 构建SDK配置
|
|
389
439
|
const config = {
|
|
390
440
|
command: command || 'claude',
|
|
@@ -398,7 +448,7 @@ async function startSdkSession(req, res) {
|
|
|
398
448
|
};
|
|
399
449
|
// 存储会话信息
|
|
400
450
|
sdkSessions.set(sessionId, {
|
|
401
|
-
agentId,
|
|
451
|
+
agentId: normalizedAgentId,
|
|
402
452
|
workspacePath,
|
|
403
453
|
sessionId,
|
|
404
454
|
config,
|
|
@@ -447,7 +497,7 @@ async function startSdkSession(req, res) {
|
|
|
447
497
|
if (sdkPid) {
|
|
448
498
|
const processManager = getAgentProcessManager();
|
|
449
499
|
await processManager.startProcess({
|
|
450
|
-
agentId,
|
|
500
|
+
agentId: normalizedAgentId,
|
|
451
501
|
sessionId,
|
|
452
502
|
pid: sdkPid,
|
|
453
503
|
mode: 'sdk',
|
|
@@ -477,12 +527,14 @@ async function startSdkSession(req, res) {
|
|
|
477
527
|
sdkSessions.delete(sessionId);
|
|
478
528
|
await removePersistedSession(sessionId);
|
|
479
529
|
}
|
|
480
|
-
if (agentForCleanup) {
|
|
481
|
-
await removePersistedSessionByAgentId(agentForCleanup);
|
|
482
|
-
}
|
|
483
530
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
484
531
|
res.status(500).json({ error: `Failed to start SDK session: ${errorMessage}` });
|
|
485
532
|
}
|
|
533
|
+
finally {
|
|
534
|
+
if (normalizedAgentId) {
|
|
535
|
+
startingAgents.delete(normalizedAgentId);
|
|
536
|
+
}
|
|
537
|
+
}
|
|
486
538
|
}
|
|
487
539
|
// ============ SDK 专用路由 ============
|
|
488
540
|
/**
|
|
@@ -800,7 +852,6 @@ terminalRouter.post('/stop', validateToken, async (req, res) => {
|
|
|
800
852
|
sdkSessions.delete(sessionId);
|
|
801
853
|
stopTerminalCommandProcess(sessionId);
|
|
802
854
|
await removePersistedSession(sessionId);
|
|
803
|
-
await removePersistedSessionByAgentId(agentId);
|
|
804
855
|
const processManager = getAgentProcessManager();
|
|
805
856
|
await processManager.removeProcess(agentId);
|
|
806
857
|
await sendStatus(agentId, null, 'stopped');
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"terminal-persistence.d.ts","sourceRoot":"","sources":["../../src/services/terminal-persistence.ts"],"names":[],"mappings":"AAAA;;;;GAIG;
|
|
1
|
+
{"version":3,"file":"terminal-persistence.d.ts","sourceRoot":"","sources":["../../src/services/terminal-persistence.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AA8BpD;;;;GAIG;AACH,wBAAgB,wBAAwB,IAAI,MAAM,CAEjD;AAED;;;;GAIG;AACH,wBAAsB,qBAAqB,IAAI,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAOzE;AAED;;;;GAIG;AACH,wBAAsB,qBAAqB,CAAC,QAAQ,EAAE,gBAAgB,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAKvF;AAED;;;;GAIG;AACH,wBAAsB,sBAAsB,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAQrF;AAED;;;;GAIG;AACH,wBAAsB,kCAAkC,CACtD,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE;IAAE,oBAAoB,EAAE,MAAM,CAAC;IAAC,mBAAmB,EAAE,MAAM,CAAA;CAAE,GACtE,OAAO,CAAC,OAAO,CAAC,CAclB;AAED;;;;GAIG;AACH,wBAAsB,sBAAsB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAgB7E;AAED;;;;;GAKG;AACH,wBAAsB,6BAA6B,CACjD,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,gBAAgB,GAAG,SAAS,CAAC,CAKvC;AAED;;;;;GAKG;AACH,wBAAsB,+BAA+B,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAgBpF"}
|
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
*
|
|
4
4
|
* 管理终端会话元数据的持久化存储
|
|
5
5
|
*/
|
|
6
|
-
import path from 'node:path';
|
|
7
|
-
import os from 'node:os';
|
|
8
6
|
import { promises as fs } from 'node:fs';
|
|
7
|
+
import os from 'node:os';
|
|
8
|
+
import path from 'node:path';
|
|
9
9
|
import { atomicWriteJsonFile, withFileLock } from '../utils/file-utils.js';
|
|
10
10
|
import { createLogger } from '../utils/logger.js';
|
|
11
11
|
const log = createLogger('terminal-persistence');
|
|
@@ -69,14 +69,10 @@ export async function savePersistedSessions(sessions) {
|
|
|
69
69
|
*/
|
|
70
70
|
export async function upsertPersistedSession(session) {
|
|
71
71
|
await updatePersistedSessions((sessions) => {
|
|
72
|
-
const runningSessions = sessions
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
}
|
|
77
|
-
else {
|
|
78
|
-
runningSessions.push(session);
|
|
79
|
-
}
|
|
72
|
+
const runningSessions = sessions
|
|
73
|
+
.filter(s => s.status === 'running')
|
|
74
|
+
.filter(s => s.sessionId !== session.sessionId && s.agentId !== session.agentId);
|
|
75
|
+
runningSessions.push(session);
|
|
80
76
|
return runningSessions;
|
|
81
77
|
});
|
|
82
78
|
}
|
|
@@ -129,7 +125,9 @@ export async function removePersistedSession(sessionId) {
|
|
|
129
125
|
*/
|
|
130
126
|
export async function findPersistedSessionByAgentId(agentId) {
|
|
131
127
|
const sessions = await loadPersistedSessions();
|
|
132
|
-
return sessions
|
|
128
|
+
return sessions
|
|
129
|
+
.filter(s => s.agentId === agentId)
|
|
130
|
+
.sort((a, b) => String(b.startedAt || '').localeCompare(String(a.startedAt || '')))[0];
|
|
133
131
|
}
|
|
134
132
|
/**
|
|
135
133
|
* 根据 agentId 移除持久化会话
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Terminal Persistence 服务单元测试
|
|
3
3
|
*/
|
|
4
|
-
import path from 'node:path';
|
|
5
4
|
import { promises as fs } from 'node:fs';
|
|
5
|
+
import path from 'node:path';
|
|
6
6
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
7
7
|
const tmpRoot = path.join(process.cwd(), '.tmp-vitest-terminal-persistence');
|
|
8
8
|
vi.mock('node:os', () => ({
|
|
@@ -110,6 +110,13 @@ describe('terminal-persistence service', () => {
|
|
|
110
110
|
};
|
|
111
111
|
expect(parseSessions('invalid json')).toEqual([]);
|
|
112
112
|
});
|
|
113
|
+
it('keeps a single running session per agent when upserting', async () => {
|
|
114
|
+
await persistence.upsertPersistedSession(makeSession('session-old', 'agent-1'));
|
|
115
|
+
await persistence.upsertPersistedSession(makeSession('session-new', 'agent-1'));
|
|
116
|
+
const sessions = await persistence.loadPersistedSessions();
|
|
117
|
+
expect(sessions).toHaveLength(1);
|
|
118
|
+
expect(sessions[0].sessionId).toBe('session-new');
|
|
119
|
+
});
|
|
113
120
|
it('serializes concurrent upserts without losing sessions', async () => {
|
|
114
121
|
await Promise.all([
|
|
115
122
|
persistence.upsertPersistedSession(makeSession('session-1', 'agent-1')),
|