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;AAenE;;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;AA0CD;;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"}
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"}
@@ -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 { removePersistedSession, removePersistedSessionByAgentId, savePersistedSessions, updatePersistedSessionAutoCommands, upsertPersistedSession, } from '../services/terminal-persistence.js';
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
- let agentForCleanup = null;
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;AAMH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AA6BpD;;;;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,CAWrF;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,CAGvC;AAED;;;;;GAKG;AACH,wBAAsB,+BAA+B,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAgBpF"}
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.filter(s => s.status === 'running');
73
- const index = runningSessions.findIndex(s => s.sessionId === session.sessionId);
74
- if (index >= 0) {
75
- runningSessions[index] = session;
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.find(s => s.agentId === agentId);
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')),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aws-runtime-bridge",
3
- "version": "1.6.14",
3
+ "version": "1.7.0",
4
4
  "description": "AgentsWorkStudio runtime bridge service for machine-level agent runtime integration",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",