aws-runtime-bridge 1.4.0 → 1.6.1
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/README.md +1 -1
- package/dist/adapter/AdapterRegistry.d.ts +1 -1
- package/dist/adapter/AdapterRegistry.d.ts.map +1 -1
- package/dist/adapter/AdapterRegistry.js +0 -2
- package/dist/adapter/ClaudeSdkAdapter.d.ts +4 -0
- package/dist/adapter/ClaudeSdkAdapter.d.ts.map +1 -1
- package/dist/adapter/ClaudeSdkAdapter.js +11 -2
- package/dist/adapter/CodexSdkAdapter.js +1 -1
- package/dist/adapter/OpencodeSdkAdapter.d.ts +13 -1
- package/dist/adapter/OpencodeSdkAdapter.d.ts.map +1 -1
- package/dist/adapter/OpencodeSdkAdapter.js +58 -6
- package/dist/adapter/OpencodeSdkAdapter.test.js +57 -1
- package/dist/adapter/types.d.ts +10 -0
- package/dist/adapter/types.d.ts.map +1 -1
- package/dist/index.js +14 -43
- package/dist/middleware/auth.d.ts +5 -0
- package/dist/middleware/auth.d.ts.map +1 -1
- package/dist/middleware/auth.js +9 -1
- package/dist/routes/file-browser.d.ts +10 -0
- package/dist/routes/file-browser.d.ts.map +1 -1
- package/dist/routes/file-browser.js +226 -4
- package/dist/routes/file-browser.test.js +31 -0
- package/dist/routes/instance.d.ts +10 -0
- package/dist/routes/instance.d.ts.map +1 -1
- package/dist/routes/instance.js +93 -2
- package/dist/routes/instance.test.js +50 -0
- package/dist/routes/pty.d.ts +106 -0
- package/dist/routes/pty.d.ts.map +1 -0
- package/dist/routes/pty.js +526 -0
- package/dist/routes/pty.test.d.ts +2 -0
- package/dist/routes/pty.test.d.ts.map +1 -0
- package/dist/routes/pty.test.js +73 -0
- package/dist/routes/sessions.d.ts +1 -1
- package/dist/routes/sessions.d.ts.map +1 -1
- package/dist/routes/sessions.js +32 -213
- package/dist/routes/terminal.d.ts +32 -3
- package/dist/routes/terminal.d.ts.map +1 -1
- package/dist/routes/terminal.js +411 -243
- package/dist/routes/terminal.test.js +105 -29
- package/dist/services/agent-process-manager.d.ts +2 -2
- package/dist/services/agent-process-manager.d.ts.map +1 -1
- package/dist/services/agent-process-manager.js +3 -3
- package/dist/services/process-detector.d.ts +2 -4
- package/dist/services/process-detector.d.ts.map +1 -1
- package/dist/services/process-detector.js +9 -16
- package/dist/services/process-registry.d.ts +2 -2
- package/dist/services/process-registry.d.ts.map +1 -1
- package/dist/services/process-registry.js +1 -1
- package/dist/services/session-output.d.ts +27 -5
- package/dist/services/session-output.d.ts.map +1 -1
- package/dist/services/session-output.js +48 -3
- package/dist/services/session-output.test.js +43 -29
- package/dist/services/terminal-persistence.d.ts +9 -0
- package/dist/services/terminal-persistence.d.ts.map +1 -1
- package/dist/services/terminal-persistence.js +20 -0
- package/dist/services/tool-installer.d.ts +10 -0
- package/dist/services/tool-installer.d.ts.map +1 -1
- package/dist/services/tool-installer.js +126 -5
- package/dist/services/tool-installer.test.js +32 -1
- package/dist/services/workspace-files.d.ts +86 -0
- package/dist/services/workspace-files.d.ts.map +1 -1
- package/dist/services/workspace-files.js +571 -21
- package/dist/services/workspace-files.test.js +471 -11
- package/dist/services/workspace-watch.d.ts +21 -0
- package/dist/services/workspace-watch.d.ts.map +1 -0
- package/dist/services/workspace-watch.js +123 -0
- package/dist/services/workspace-watch.test.d.ts +2 -0
- package/dist/services/workspace-watch.test.d.ts.map +1 -0
- package/dist/services/workspace-watch.test.js +38 -0
- package/dist/types.d.ts +8 -4
- package/dist/types.d.ts.map +1 -1
- package/package.json +9 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest';
|
|
2
2
|
import { SDK_PROVIDER_DEFINITIONS } from '../adapter/SdkProviderSpi.js';
|
|
3
|
-
import { resolveSdkProviderId } from './terminal.js';
|
|
3
|
+
import { buildRuntimeEnv, createTerminalOutputDecoder, decodeTerminalOutputChunk, formatTerminalPrompt, formatSdkOutputEvent, normalizeTerminalCommandInput, parseTerminalDirectoryChangeTarget, resolveTerminalOutputEncoding, resolveSdkProviderId, } from './terminal.js';
|
|
4
4
|
describe('terminal route validation', () => {
|
|
5
5
|
it('requires agentId and workspacePath for start', () => {
|
|
6
6
|
const validateStartRequest = (body) => {
|
|
@@ -11,12 +11,32 @@ describe('terminal route validation', () => {
|
|
|
11
11
|
expect(validateStartRequest({}).valid).toBe(false);
|
|
12
12
|
expect(validateStartRequest({ agentId: 'agent-1', workspacePath: '/path' }).valid).toBe(true);
|
|
13
13
|
});
|
|
14
|
-
it('uses default command when not provided', () => {
|
|
14
|
+
it('uses SDK default command when not provided', () => {
|
|
15
15
|
const resolveCommand = (command) => command || 'claude';
|
|
16
16
|
expect(resolveCommand(undefined)).toBe('claude');
|
|
17
|
-
expect(resolveCommand('
|
|
17
|
+
expect(resolveCommand('codex')).toBe('codex');
|
|
18
18
|
});
|
|
19
|
-
it('
|
|
19
|
+
it('uses SDK launch mode for unsupported mode values', () => {
|
|
20
|
+
const resolveLaunchMode = (mode) => (mode === 'sdk' ? 'sdk' : 'sdk');
|
|
21
|
+
expect(resolveLaunchMode(undefined)).toBe('sdk');
|
|
22
|
+
expect(resolveLaunchMode('sdk')).toBe('sdk');
|
|
23
|
+
expect(resolveLaunchMode('unsupported')).toBe('sdk');
|
|
24
|
+
});
|
|
25
|
+
it('keeps shortcut mode available for manual key injection', () => {
|
|
26
|
+
const buildInputResponse = (mode) => mode === 'shortcut' ? { ok: true, mode: 'shortcut' } : { ok: true, mode: 'sdk' };
|
|
27
|
+
expect(buildInputResponse('shortcut')).toEqual({ ok: true, mode: 'shortcut' });
|
|
28
|
+
expect(buildInputResponse(undefined)).toEqual({ ok: true, mode: 'sdk' });
|
|
29
|
+
});
|
|
30
|
+
it('does not downgrade unsupported shortcut input to normal SDK messages', () => {
|
|
31
|
+
const buildShortcutFallbackResponse = (hasShortcutSupport) => (hasShortcutSupport
|
|
32
|
+
? { status: 200, body: { ok: true, mode: 'shortcut' } }
|
|
33
|
+
: { status: 400, body: { error: 'shortcut input is not supported by this SDK provider' } });
|
|
34
|
+
expect(buildShortcutFallbackResponse(false)).toEqual({
|
|
35
|
+
status: 400,
|
|
36
|
+
body: { error: 'shortcut input is not supported by this SDK provider' },
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
it('requires sessionId and input for SDK terminal input', () => {
|
|
20
40
|
const validateInputRequest = (body) => {
|
|
21
41
|
if (!body.sessionId || body.input === undefined || body.input === null) {
|
|
22
42
|
return { valid: false, error: 'sessionId and input are required' };
|
|
@@ -24,16 +44,11 @@ describe('terminal route validation', () => {
|
|
|
24
44
|
return { valid: true };
|
|
25
45
|
};
|
|
26
46
|
expect(validateInputRequest({}).valid).toBe(false);
|
|
27
|
-
expect(validateInputRequest({ sessionId: 'session-1', input: '
|
|
47
|
+
expect(validateInputRequest({ sessionId: 'session-1', input: 'continue' }).valid).toBe(true);
|
|
28
48
|
});
|
|
29
|
-
it('
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
return { valid: false, error: 'sessionId, cols, rows are required' };
|
|
33
|
-
return { valid: true };
|
|
34
|
-
};
|
|
35
|
-
expect(validateResizeRequest({}).valid).toBe(false);
|
|
36
|
-
expect(validateResizeRequest({ sessionId: 'session-1', cols: 120, rows: 30 }).valid).toBe(true);
|
|
49
|
+
it('keeps resize contract as SDK no-op compatibility endpoint', () => {
|
|
50
|
+
const buildResizeResponse = (cols, rows) => ({ ok: true, mode: 'sdk', noop: true, cols, rows });
|
|
51
|
+
expect(buildResizeResponse(120, 30)).toEqual({ ok: true, mode: 'sdk', noop: true, cols: 120, rows: 30 });
|
|
37
52
|
});
|
|
38
53
|
it('requires sessionId for stop', () => {
|
|
39
54
|
const validateStopRequest = (body) => {
|
|
@@ -46,26 +61,53 @@ describe('terminal route validation', () => {
|
|
|
46
61
|
});
|
|
47
62
|
});
|
|
48
63
|
describe('terminal configuration', () => {
|
|
49
|
-
it('
|
|
50
|
-
const
|
|
51
|
-
if (platform === 'win32')
|
|
52
|
-
return { shell: 'powershell.exe', args: ['-NoLogo'] };
|
|
53
|
-
return { shell: 'bash', args: [] };
|
|
54
|
-
};
|
|
55
|
-
expect(getShellConfig('win32').shell).toBe('powershell.exe');
|
|
56
|
-
expect(getShellConfig('linux').shell).toBe('bash');
|
|
57
|
-
});
|
|
58
|
-
it('builds correct terminal environment', () => {
|
|
59
|
-
const buildTerminalEnv = (agentId, baseEnv) => ({
|
|
60
|
-
...baseEnv,
|
|
61
|
-
AWS_AGENT_ID: agentId,
|
|
62
|
-
AWS_MCP_CLAIM_LAUNCH_BINDING: 'true',
|
|
63
|
-
});
|
|
64
|
-
const env = buildTerminalEnv('agent-123', { PATH: '/usr/bin' });
|
|
64
|
+
it('builds correct SDK runtime environment', () => {
|
|
65
|
+
const env = buildRuntimeEnv('agent-123', undefined, { PATH: '/usr/bin' });
|
|
65
66
|
expect(env.AWS_AGENT_ID).toBe('agent-123');
|
|
66
67
|
expect(env.AWS_MCP_LAUNCH_BINDING_ID).toBeUndefined();
|
|
67
68
|
expect(env.AWS_MCP_CLAIM_LAUNCH_BINDING).toBe('true');
|
|
68
69
|
});
|
|
70
|
+
it('merges launch env overrides without allowing agent identity override', () => {
|
|
71
|
+
const env = buildRuntimeEnv('agent-123', '/workspace', { PATH: '/usr/bin' }, {
|
|
72
|
+
ANTHROPIC_BASE_URL: ' https://api.example.com ',
|
|
73
|
+
ANTHROPIC_AUTH_TOKEN: 'secret-token',
|
|
74
|
+
ANTHROPIC_MODEL: 'claude-sonnet-4-5',
|
|
75
|
+
AWS_AGENT_ID: 'malicious-agent',
|
|
76
|
+
EMPTY_VALUE: ' ',
|
|
77
|
+
});
|
|
78
|
+
expect(env.ANTHROPIC_BASE_URL).toBe('https://api.example.com');
|
|
79
|
+
expect(env.ANTHROPIC_AUTH_TOKEN).toBe('secret-token');
|
|
80
|
+
expect(env.ANTHROPIC_MODEL).toBe('claude-sonnet-4-5');
|
|
81
|
+
expect(env.EMPTY_VALUE).toBeUndefined();
|
|
82
|
+
expect(env.AWS_AGENT_ID).toBe('agent-123');
|
|
83
|
+
expect(env.AWS_WORKSPACE_PATH).toBe('/workspace');
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
describe('terminal command helpers', () => {
|
|
87
|
+
it('normalizes carriage-return terminal input into executable command text', () => {
|
|
88
|
+
expect(normalizeTerminalCommandInput('npm test\r')).toBe('npm test');
|
|
89
|
+
expect(normalizeTerminalCommandInput(' pwd \n')).toBe('pwd');
|
|
90
|
+
});
|
|
91
|
+
it('parses cd commands without treating other shell commands as directory changes', () => {
|
|
92
|
+
expect(parseTerminalDirectoryChangeTarget('cd')).toBeNull();
|
|
93
|
+
expect(parseTerminalDirectoryChangeTarget('cd /d "D:\\code\\repo"')).toBe('D:\\code\\repo');
|
|
94
|
+
expect(parseTerminalDirectoryChangeTarget('npm run build')).toBeUndefined();
|
|
95
|
+
});
|
|
96
|
+
it('formats the working directory prompt as terminal output text', () => {
|
|
97
|
+
expect(formatTerminalPrompt('D:\\code\\repo')).toBe('\x1b[36mD:\\code\\repo>\x1b[0m ');
|
|
98
|
+
});
|
|
99
|
+
it('defaults Windows terminal command output to GB18030 decoding', () => {
|
|
100
|
+
expect(resolveTerminalOutputEncoding('win32', {})).toBe('gb18030');
|
|
101
|
+
expect(resolveTerminalOutputEncoding('linux', {})).toBe('utf-8');
|
|
102
|
+
expect(resolveTerminalOutputEncoding('win32', { AWS_TERMINAL_OUTPUT_ENCODING: 'utf-8' })).toBe('utf-8');
|
|
103
|
+
});
|
|
104
|
+
it('decodes GB18030 Chinese output across chunk boundaries', () => {
|
|
105
|
+
const decoder = createTerminalOutputDecoder('gb18030');
|
|
106
|
+
const first = decodeTerminalOutputChunk(decoder, Buffer.from([0xd6]));
|
|
107
|
+
const second = decodeTerminalOutputChunk(decoder, Buffer.from([0xd0, 0xce, 0xc4]));
|
|
108
|
+
const flushed = decodeTerminalOutputChunk(decoder, undefined, true);
|
|
109
|
+
expect(`${first}${second}${flushed}`).toBe('中文');
|
|
110
|
+
});
|
|
69
111
|
});
|
|
70
112
|
describe('resolveSdkProviderId', () => {
|
|
71
113
|
it('exposes codex through the SDK provider SPI', () => {
|
|
@@ -88,3 +130,37 @@ describe('resolveSdkProviderId', () => {
|
|
|
88
130
|
expect(resolveSdkProviderId(undefined)).toBe('claude-code');
|
|
89
131
|
});
|
|
90
132
|
});
|
|
133
|
+
describe('SDK output forwarding', () => {
|
|
134
|
+
it('formats text and thinking provider events for the terminal output callback path', () => {
|
|
135
|
+
const textEvent = {
|
|
136
|
+
type: 'text_delta',
|
|
137
|
+
sessionId: 'session-1',
|
|
138
|
+
timestamp: new Date(0).toISOString(),
|
|
139
|
+
data: { text: 'hello' },
|
|
140
|
+
};
|
|
141
|
+
const thinkingEvent = {
|
|
142
|
+
type: 'thinking',
|
|
143
|
+
sessionId: 'session-1',
|
|
144
|
+
timestamp: new Date(0).toISOString(),
|
|
145
|
+
data: { text: 'thinking...' },
|
|
146
|
+
};
|
|
147
|
+
expect(formatSdkOutputEvent(textEvent)).toBe('hello');
|
|
148
|
+
expect(formatSdkOutputEvent(thinkingEvent)).toBe('thinking...');
|
|
149
|
+
});
|
|
150
|
+
it('formats SDK errors as terminal output and ignores non-output events', () => {
|
|
151
|
+
const errorEvent = {
|
|
152
|
+
type: 'error',
|
|
153
|
+
sessionId: 'session-1',
|
|
154
|
+
timestamp: new Date(0).toISOString(),
|
|
155
|
+
data: { text: 'boom' },
|
|
156
|
+
};
|
|
157
|
+
const toolEvent = {
|
|
158
|
+
type: 'tool_use_start',
|
|
159
|
+
sessionId: 'session-1',
|
|
160
|
+
timestamp: new Date(0).toISOString(),
|
|
161
|
+
data: { toolName: 'read_file' },
|
|
162
|
+
};
|
|
163
|
+
expect(formatSdkOutputEvent(errorEvent)).toBe('\r\n[SDK Error] boom\r\n');
|
|
164
|
+
expect(formatSdkOutputEvent(toolEvent)).toBeUndefined();
|
|
165
|
+
});
|
|
166
|
+
});
|
|
@@ -14,7 +14,7 @@ export interface ProcessConfig {
|
|
|
14
14
|
agentId: string;
|
|
15
15
|
sessionId: string;
|
|
16
16
|
pid: number;
|
|
17
|
-
mode: '
|
|
17
|
+
mode: 'sdk';
|
|
18
18
|
workspacePath: string;
|
|
19
19
|
command: string;
|
|
20
20
|
}
|
|
@@ -201,7 +201,7 @@ export declare class AgentProcessManager {
|
|
|
201
201
|
pid?: number;
|
|
202
202
|
workspacePath?: string;
|
|
203
203
|
command?: string;
|
|
204
|
-
mode?: '
|
|
204
|
+
mode?: 'sdk';
|
|
205
205
|
}>): Promise<void>;
|
|
206
206
|
/**
|
|
207
207
|
* Get process statistics
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent-process-manager.d.ts","sourceRoot":"","sources":["../../src/services/agent-process-manager.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAKH,OAAO,EAAsB,KAAK,aAAa,EAAE,KAAK,YAAY,EAAE,KAAK,YAAY,EAAE,MAAM,uBAAuB,CAAC;AACrH,OAAO,EAOL,KAAK,WAAW,EAChB,KAAK,mBAAmB,EACzB,MAAM,uBAAuB,CAAC;AAK/B;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,KAAK,
|
|
1
|
+
{"version":3,"file":"agent-process-manager.d.ts","sourceRoot":"","sources":["../../src/services/agent-process-manager.ts"],"names":[],"mappings":"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"}
|
|
@@ -482,7 +482,7 @@ export class AgentProcessManager {
|
|
|
482
482
|
agentId,
|
|
483
483
|
sessionId: session?.sessionId || `reclaimed-${Date.now()}`,
|
|
484
484
|
pid: orphan.pid,
|
|
485
|
-
mode:
|
|
485
|
+
mode: 'sdk',
|
|
486
486
|
state: 'running',
|
|
487
487
|
workspacePath: session?.workspacePath || orphan.cwd || '',
|
|
488
488
|
command: orphan.command || session?.command || '',
|
|
@@ -521,7 +521,7 @@ export class AgentProcessManager {
|
|
|
521
521
|
agentId,
|
|
522
522
|
sessionId: session?.sessionId || `reclaimed-${Date.now()}`,
|
|
523
523
|
pid: orphan.pid,
|
|
524
|
-
mode:
|
|
524
|
+
mode: 'sdk',
|
|
525
525
|
state: 'running',
|
|
526
526
|
workspacePath: session?.workspacePath || orphan.cwd || '',
|
|
527
527
|
command: orphan.command || session?.command || '',
|
|
@@ -696,7 +696,7 @@ export class AgentProcessManager {
|
|
|
696
696
|
agentId: session.agentId,
|
|
697
697
|
sessionId: session.sessionId,
|
|
698
698
|
pid: session.pid,
|
|
699
|
-
mode:
|
|
699
|
+
mode: 'sdk',
|
|
700
700
|
state,
|
|
701
701
|
workspacePath: session.workspacePath || '',
|
|
702
702
|
command: session.command || '',
|
|
@@ -32,9 +32,7 @@ export declare function findClaudeCodeProcesses(): ProcessInfo[];
|
|
|
32
32
|
* 检测策略(按优先级):
|
|
33
33
|
* 1. 通过命令行中的 agentId 直接匹配
|
|
34
34
|
* 2. Windows: 通过 PowerShell 在命令行中模糊搜索 agentId
|
|
35
|
-
* 3. Windows:
|
|
36
|
-
* PTY 启动链: pty.spawn('powershell.exe', {env: {AWS_AGENT_ID}}) → 用户运行 'claude'
|
|
37
|
-
* 此时 claude 是 powershell 子进程,agentId 仅存在于父进程的环境变量中
|
|
35
|
+
* 3. Windows: 通过进程树搜索,覆盖 agentId 仅存在于祖先进程环境变量中的场景
|
|
38
36
|
*
|
|
39
37
|
* @param agentId Agent ID
|
|
40
38
|
* @returns 进程信息或 undefined
|
|
@@ -46,7 +44,7 @@ export declare function findOrphanProcessByAgentId(agentId: string): ProcessInfo
|
|
|
46
44
|
* 当 agentId 无法匹配到进程时,通过工作目录路径做宽泛搜索。
|
|
47
45
|
* 适用于以下场景:
|
|
48
46
|
* - runtime-bridge 重启后持久化会话丢失
|
|
49
|
-
* -
|
|
47
|
+
* - agentId 可能仅在祖先进程环境变量中,命令行搜索不到
|
|
50
48
|
* - 进程树断链,无法通过祖先链匹配
|
|
51
49
|
*
|
|
52
50
|
* @param workspacePath 工作目录路径
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"process-detector.d.ts","sourceRoot":"","sources":["../../src/services/process-detector.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAiCH;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAoBrD;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,IAAI,WAAW,EAAE,CAkBvD;AA8LD
|
|
1
|
+
{"version":3,"file":"process-detector.d.ts","sourceRoot":"","sources":["../../src/services/process-detector.ts"],"names":[],"mappings":"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"}
|
|
@@ -241,9 +241,7 @@ function extractAgentId(cmd) {
|
|
|
241
241
|
* 检测策略(按优先级):
|
|
242
242
|
* 1. 通过命令行中的 agentId 直接匹配
|
|
243
243
|
* 2. Windows: 通过 PowerShell 在命令行中模糊搜索 agentId
|
|
244
|
-
* 3. Windows:
|
|
245
|
-
* PTY 启动链: pty.spawn('powershell.exe', {env: {AWS_AGENT_ID}}) → 用户运行 'claude'
|
|
246
|
-
* 此时 claude 是 powershell 子进程,agentId 仅存在于父进程的环境变量中
|
|
244
|
+
* 3. Windows: 通过进程树搜索,覆盖 agentId 仅存在于祖先进程环境变量中的场景
|
|
247
245
|
*
|
|
248
246
|
* @param agentId Agent ID
|
|
249
247
|
* @returns 进程信息或 undefined
|
|
@@ -301,18 +299,14 @@ function findProcessByEnvVarWindows(agentId) {
|
|
|
301
299
|
return undefined;
|
|
302
300
|
}
|
|
303
301
|
/**
|
|
304
|
-
* Windows:
|
|
302
|
+
* Windows: 通过祖先环境变量搜索孤儿进程。
|
|
305
303
|
*
|
|
306
304
|
* 场景说明:
|
|
307
|
-
*
|
|
308
|
-
*
|
|
309
|
-
* 然后用户在 shell 中执行 'claude' 命令,产生如下进程树:
|
|
310
|
-
* node.exe (runtime-bridge, PID=R)
|
|
311
|
-
* └── powershell.exe (PID=A, 有 AWS_AGENT_ID 环境变量)
|
|
312
|
-
* └── claude.exe (PID=B, 无 AWS_AGENT_ID, 命令行含 'claude')
|
|
305
|
+
* runtime-bridge 或其子进程可能把 AWS_AGENT_ID 传给 shell/agent 祖先进程,
|
|
306
|
+
* 最终运行的 claude/opencode/codex 进程命令行里不一定包含 agentId。
|
|
313
307
|
*
|
|
314
308
|
* 此时:
|
|
315
|
-
* - findProcessesWindows() 能找到
|
|
309
|
+
* - findProcessesWindows() 能找到 agent CLI 进程,但 extractAgentId() 提取不到 agentId
|
|
316
310
|
* - findProcessByEnvVarWindows() 在命令行中搜不到 agentId(环境变量不在命令行中)
|
|
317
311
|
*
|
|
318
312
|
* 策略:用极简 PowerShell 获取全量进程树数据 → 在 Node.js 层做祖先链匹配
|
|
@@ -429,8 +423,7 @@ function searchOrphanInProcessTree(rawJson, agentId) {
|
|
|
429
423
|
const shellInfo = findAncestorShell(proc.processId);
|
|
430
424
|
if (shellInfo) {
|
|
431
425
|
// ★ 验证 agentId:检查 shell 命令行中是否包含目标 agentId
|
|
432
|
-
//
|
|
433
|
-
// 如果无法验证,仍然返回(保持向后兼容)
|
|
426
|
+
// shell 的命令行中不一定直接包含 agentId,但可以尝试匹配。
|
|
434
427
|
const shellCmdLower = shellInfo.shellCmd.toLowerCase();
|
|
435
428
|
const cmdLineIncludesAgentId = shellCmdLower.includes(agentId.toLowerCase());
|
|
436
429
|
if (cmdLineIncludesAgentId) {
|
|
@@ -501,7 +494,7 @@ function extractJsonFromOutput(output) {
|
|
|
501
494
|
*
|
|
502
495
|
* Windows 下无法直接读取其他进程的环境变量,但可以通过以下方式间接验证:
|
|
503
496
|
* 1. 检查进程命令行中是否包含 agentId
|
|
504
|
-
* 2. 检查父进程命令行中是否包含 agentId
|
|
497
|
+
* 2. 检查父进程命令行中是否包含 agentId
|
|
505
498
|
* 3. 检查进程的整个祖先链中是否有匹配的 runtime-bridge 启动的 shell
|
|
506
499
|
*
|
|
507
500
|
* @param pid 进程 PID
|
|
@@ -520,7 +513,7 @@ function verifyProcessAgentId(pid, targetAgentId) {
|
|
|
520
513
|
const result = execPowerShell(psScript, 5000).trim();
|
|
521
514
|
if (result === 'MATCH')
|
|
522
515
|
return true;
|
|
523
|
-
// 策略2:检查父进程命令行是否包含 agentId
|
|
516
|
+
// 策略2:检查父进程命令行是否包含 agentId
|
|
524
517
|
const parentPsScript = `
|
|
525
518
|
try {
|
|
526
519
|
$proc = Get-CimInstance Win32_Process -Filter "ProcessId = ${pid}" -ErrorAction SilentlyContinue
|
|
@@ -563,7 +556,7 @@ function verifyProcessAgentId(pid, targetAgentId) {
|
|
|
563
556
|
* 当 agentId 无法匹配到进程时,通过工作目录路径做宽泛搜索。
|
|
564
557
|
* 适用于以下场景:
|
|
565
558
|
* - runtime-bridge 重启后持久化会话丢失
|
|
566
|
-
* -
|
|
559
|
+
* - agentId 可能仅在祖先进程环境变量中,命令行搜索不到
|
|
567
560
|
* - 进程树断链,无法通过祖先链匹配
|
|
568
561
|
*
|
|
569
562
|
* @param workspacePath 工作目录路径
|
|
@@ -22,7 +22,7 @@ export interface ProcessRecord {
|
|
|
22
22
|
agentId: string;
|
|
23
23
|
sessionId: string;
|
|
24
24
|
pid: number;
|
|
25
|
-
mode: '
|
|
25
|
+
mode: 'sdk';
|
|
26
26
|
state: ProcessState;
|
|
27
27
|
workspacePath: string;
|
|
28
28
|
command: string;
|
|
@@ -41,7 +41,7 @@ export interface PersistedSessionInput {
|
|
|
41
41
|
pid?: number;
|
|
42
42
|
workspacePath?: string;
|
|
43
43
|
command?: string;
|
|
44
|
-
mode?: '
|
|
44
|
+
mode?: 'sdk';
|
|
45
45
|
}
|
|
46
46
|
/**
|
|
47
47
|
* 获取持久化注册表文件路径
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"process-registry.d.ts","sourceRoot":"","sources":["../../src/services/process-registry.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAUH;;GAEG;AACH,MAAM,MAAM,YAAY,GAAG,UAAU,GAAG,SAAS,GAAG,UAAU,GAAG,UAAU,GAAG,YAAY,GAAG,SAAS,CAAC;AAEvG;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,KAAK,
|
|
1
|
+
{"version":3,"file":"process-registry.d.ts","sourceRoot":"","sources":["../../src/services/process-registry.ts"],"names":[],"mappings":"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"}
|
|
@@ -280,7 +280,7 @@ export class ProcessRegistry {
|
|
|
280
280
|
agentId: session.agentId,
|
|
281
281
|
sessionId: session.sessionId,
|
|
282
282
|
pid: session.pid,
|
|
283
|
-
mode:
|
|
283
|
+
mode: 'sdk',
|
|
284
284
|
state: 'unknown', // 重建时状态未知,需要外部检测
|
|
285
285
|
workspacePath: session.workspacePath || '',
|
|
286
286
|
command: session.command || '',
|
|
@@ -4,6 +4,13 @@
|
|
|
4
4
|
* 管理终端会话的输出缓冲和刷新机制
|
|
5
5
|
*/
|
|
6
6
|
import type { Session } from '../types.js';
|
|
7
|
+
interface RuntimeStatusActionInfo {
|
|
8
|
+
actionType?: string;
|
|
9
|
+
actionLabel?: string;
|
|
10
|
+
actionDetail?: string;
|
|
11
|
+
actionResult?: string;
|
|
12
|
+
actionId?: string;
|
|
13
|
+
}
|
|
7
14
|
/** 活跃会话存储 */
|
|
8
15
|
export declare const sessions: Map<string, Session>;
|
|
9
16
|
/**
|
|
@@ -14,11 +21,7 @@ export declare const sessions: Map<string, Session>;
|
|
|
14
21
|
* @param status - 状态
|
|
15
22
|
* @param actionInfo - 可选的动作详情
|
|
16
23
|
*/
|
|
17
|
-
export declare function sendStatus(agentId: string, sessionId: string | null, status: string, actionInfo?: {
|
|
18
|
-
actionType?: string;
|
|
19
|
-
actionLabel?: string;
|
|
20
|
-
actionDetail?: string;
|
|
21
|
-
}, usage?: {
|
|
24
|
+
export declare function sendStatus(agentId: string, sessionId: string | null, status: string, actionInfo?: RuntimeStatusActionInfo, usage?: {
|
|
22
25
|
inputTokens: number;
|
|
23
26
|
outputTokens: number;
|
|
24
27
|
}): Promise<void | {
|
|
@@ -41,6 +44,24 @@ export declare function sendOutput(agentId: string, output: string, sessionId: s
|
|
|
41
44
|
* @param questions - 结构化问题列表
|
|
42
45
|
*/
|
|
43
46
|
export declare function sendQuestionRequest(agentId: string, sessionId: string, questions: unknown[]): Promise<void>;
|
|
47
|
+
export interface RuntimeFileChangedPayload {
|
|
48
|
+
bridgeBaseUrl?: string;
|
|
49
|
+
workspacePath: string;
|
|
50
|
+
filePath: string;
|
|
51
|
+
eventType: string;
|
|
52
|
+
size?: number;
|
|
53
|
+
mtimeMs?: number;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* 发送工作区文件变更事件到调度器,由调度器通过 WebSocket 通知前端刷新预览。
|
|
57
|
+
*/
|
|
58
|
+
export declare function sendFileChanged(payload: RuntimeFileChangedPayload): Promise<void>;
|
|
59
|
+
/**
|
|
60
|
+
* 追加会话输出到有界缓冲区。
|
|
61
|
+
*
|
|
62
|
+
* 主流程:拼接新输出 -> 超过上限时丢弃最旧终端噪声并保留截断提示 -> 等待 flush pump 批量发送。
|
|
63
|
+
*/
|
|
64
|
+
export declare function appendSessionOutput(sessionId: string, data: string): void;
|
|
44
65
|
/**
|
|
45
66
|
* 调度输出刷新
|
|
46
67
|
*
|
|
@@ -53,4 +74,5 @@ export declare function scheduleOutputFlush(sessionId: string): void;
|
|
|
53
74
|
* @param sessionId - 会话 ID
|
|
54
75
|
*/
|
|
55
76
|
export declare function flushSessionOutput(sessionId: string): Promise<void>;
|
|
77
|
+
export {};
|
|
56
78
|
//# sourceMappingURL=session-output.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-output.d.ts","sourceRoot":"","sources":["../../src/services/session-output.ts"],"names":[],"mappings":"AAAA;;;;GAIG;
|
|
1
|
+
{"version":3,"file":"session-output.d.ts","sourceRoot":"","sources":["../../src/services/session-output.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAG3C,UAAU,uBAAuB;IAC/B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAcD,aAAa;AACb,eAAO,MAAM,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAa,CAAC;AAExD;;;;;;;GAOG;AACH,wBAAsB,UAAU,CAC9B,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,GAAG,IAAI,EACxB,MAAM,EAAE,MAAM,EACd,UAAU,CAAC,EAAE,uBAAuB,EACpC,KAAK,CAAC,EAAE;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,GACpD,OAAO,CAAC,IAAI,GAAG;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,CAAC,CAuBnC;AAED;;;;;;;GAOG;AACH,wBAAsB,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAU/G;AAED;;;;;;GAMG;AACH,wBAAsB,mBAAmB,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAUjH;AAED,MAAM,WAAW,yBAAyB;IACxC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,wBAAsB,eAAe,CAAC,OAAO,EAAE,yBAAyB,GAAG,OAAO,CAAC,IAAI,CAAC,CAUvF;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAgBzE;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAa3D;AAED;;;;GAIG;AACH,wBAAsB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAqCzE"}
|
|
@@ -10,6 +10,12 @@ function runtimeAuthHeaders() {
|
|
|
10
10
|
const token = getRuntimeAccessToken(undefined, schedulerBaseUrl) || getRuntimeAccessToken();
|
|
11
11
|
return token ? { 'X-Runtime-Token': token } : undefined;
|
|
12
12
|
}
|
|
13
|
+
/** 输出刷新防抖时间:合并启动期高频小块输出,降低 HTTP/WebSocket 压力。 */
|
|
14
|
+
const OUTPUT_FLUSH_DEBOUNCE_MS = 100;
|
|
15
|
+
/** 单个会话待发送输出上限,避免调度器不可达时内存无限增长。 */
|
|
16
|
+
const MAX_PENDING_OUTPUT_CHARS = 64 * 1024;
|
|
17
|
+
/** 单次回调最大发送量,避免一次 POST 携带过大的终端噪声。 */
|
|
18
|
+
const MAX_OUTPUT_FLUSH_CHARS = 32 * 1024;
|
|
13
19
|
/** 活跃会话存储 */
|
|
14
20
|
export const sessions = new Map();
|
|
15
21
|
/**
|
|
@@ -30,6 +36,10 @@ export async function sendStatus(agentId, sessionId, status, actionInfo, usage)
|
|
|
30
36
|
payload.actionLabel = actionInfo.actionLabel;
|
|
31
37
|
if (actionInfo.actionDetail)
|
|
32
38
|
payload.actionDetail = actionInfo.actionDetail;
|
|
39
|
+
if (actionInfo.actionResult)
|
|
40
|
+
payload.actionResult = actionInfo.actionResult;
|
|
41
|
+
if (actionInfo.actionId)
|
|
42
|
+
payload.actionId = actionInfo.actionId;
|
|
33
43
|
}
|
|
34
44
|
// 添加 token 用量信息
|
|
35
45
|
if (usage) {
|
|
@@ -81,6 +91,41 @@ export async function sendQuestionRequest(agentId, sessionId, questions) {
|
|
|
81
91
|
// ignore transient callback failures
|
|
82
92
|
}
|
|
83
93
|
}
|
|
94
|
+
/**
|
|
95
|
+
* 发送工作区文件变更事件到调度器,由调度器通过 WebSocket 通知前端刷新预览。
|
|
96
|
+
*/
|
|
97
|
+
export async function sendFileChanged(payload) {
|
|
98
|
+
try {
|
|
99
|
+
const headers = runtimeAuthHeaders();
|
|
100
|
+
if (!headers) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
await axios.post(`${schedulerBaseUrl}/api/runtime/callback/file-changed`, payload, { headers });
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
// ignore transient callback failures
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* 追加会话输出到有界缓冲区。
|
|
111
|
+
*
|
|
112
|
+
* 主流程:拼接新输出 -> 超过上限时丢弃最旧终端噪声并保留截断提示 -> 等待 flush pump 批量发送。
|
|
113
|
+
*/
|
|
114
|
+
export function appendSessionOutput(sessionId, data) {
|
|
115
|
+
const session = sessions.get(sessionId);
|
|
116
|
+
if (!session || !data) {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
const combinedOutput = `${session.outputBuffer}${data}`;
|
|
120
|
+
if (combinedOutput.length <= MAX_PENDING_OUTPUT_CHARS) {
|
|
121
|
+
session.outputBuffer = combinedOutput;
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
const omittedChars = combinedOutput.length - MAX_PENDING_OUTPUT_CHARS;
|
|
125
|
+
const marker = `\r\n[AgentsWorkStudio: omitted ${omittedChars} chars of old terminal output]\r\n`;
|
|
126
|
+
const retainedLength = Math.max(0, MAX_PENDING_OUTPUT_CHARS - marker.length);
|
|
127
|
+
session.outputBuffer = `${marker}${combinedOutput.slice(-retainedLength)}`;
|
|
128
|
+
}
|
|
84
129
|
/**
|
|
85
130
|
* 调度输出刷新
|
|
86
131
|
*
|
|
@@ -97,7 +142,7 @@ export function scheduleOutputFlush(sessionId) {
|
|
|
97
142
|
session.flushTimer = setTimeout(() => {
|
|
98
143
|
session.flushTimer = null;
|
|
99
144
|
flushSessionOutput(sessionId).catch(() => undefined);
|
|
100
|
-
},
|
|
145
|
+
}, OUTPUT_FLUSH_DEBOUNCE_MS);
|
|
101
146
|
}
|
|
102
147
|
/**
|
|
103
148
|
* 刷新会话输出
|
|
@@ -115,8 +160,8 @@ export async function flushSessionOutput(sessionId) {
|
|
|
115
160
|
if (!session.outputBuffer) {
|
|
116
161
|
return;
|
|
117
162
|
}
|
|
118
|
-
const output = session.outputBuffer;
|
|
119
|
-
session.outputBuffer =
|
|
163
|
+
const output = session.outputBuffer.slice(0, MAX_OUTPUT_FLUSH_CHARS);
|
|
164
|
+
session.outputBuffer = session.outputBuffer.slice(MAX_OUTPUT_FLUSH_CHARS);
|
|
120
165
|
const running = (async () => {
|
|
121
166
|
const seq = session.seq + 1;
|
|
122
167
|
session.seq = seq;
|
|
@@ -1,11 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Session Output 服务单元测试
|
|
3
3
|
*/
|
|
4
|
-
import {
|
|
4
|
+
import { afterEach, describe, expect, it } from 'vitest';
|
|
5
|
+
import { appendSessionOutput, sessions } from './session-output.js';
|
|
6
|
+
const createSession = (overrides = {}) => ({
|
|
7
|
+
agentId: 'agent-1',
|
|
8
|
+
workspacePath: 'D:/workspace',
|
|
9
|
+
outputBuffer: '',
|
|
10
|
+
flushTimer: null,
|
|
11
|
+
flushInFlight: null,
|
|
12
|
+
seq: 0,
|
|
13
|
+
...overrides,
|
|
14
|
+
});
|
|
15
|
+
afterEach(() => {
|
|
16
|
+
sessions.clear();
|
|
17
|
+
});
|
|
5
18
|
describe('session-output service', () => {
|
|
6
19
|
it('manages sessions map correctly', () => {
|
|
7
|
-
|
|
8
|
-
sessions.set('session-1', { agentId: 'agent-1', outputBuffer: '' });
|
|
20
|
+
sessions.set('session-1', createSession());
|
|
9
21
|
expect(sessions.size).toBe(1);
|
|
10
22
|
expect(sessions.has('session-1')).toBe(true);
|
|
11
23
|
const session = sessions.get('session-1');
|
|
@@ -16,10 +28,8 @@ describe('session-output service', () => {
|
|
|
16
28
|
expect(sessions.size).toBe(0);
|
|
17
29
|
});
|
|
18
30
|
it('finds session by agentId', () => {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
['session-2', { agentId: 'agent-2' }],
|
|
22
|
-
]);
|
|
31
|
+
sessions.set('session-1', createSession({ agentId: 'agent-1' }));
|
|
32
|
+
sessions.set('session-2', createSession({ agentId: 'agent-2' }));
|
|
23
33
|
const findByAgentId = (agentId) => {
|
|
24
34
|
for (const [id, session] of sessions) {
|
|
25
35
|
if (session.agentId === agentId)
|
|
@@ -31,38 +41,42 @@ describe('session-output service', () => {
|
|
|
31
41
|
expect(findByAgentId('agent-3')).toBe(null);
|
|
32
42
|
});
|
|
33
43
|
it('accumulates output correctly', () => {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
expect(outputBuffer).toBe('line1\nline2\n');
|
|
44
|
+
sessions.set('session-1', createSession());
|
|
45
|
+
appendSessionOutput('session-1', 'line1\n');
|
|
46
|
+
appendSessionOutput('session-1', 'line2\n');
|
|
47
|
+
expect(sessions.get('session-1')?.outputBuffer).toBe('line1\nline2\n');
|
|
39
48
|
});
|
|
40
|
-
it('
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
const flushed = flushBuffer();
|
|
48
|
-
expect(flushed).toBe('pending output');
|
|
49
|
-
expect(outputBuffer).toBe('');
|
|
49
|
+
it('bounds pending SDK output so callback failures cannot grow memory without limit', () => {
|
|
50
|
+
sessions.set('session-bounded', createSession());
|
|
51
|
+
appendSessionOutput('session-bounded', 'a'.repeat(70 * 1024));
|
|
52
|
+
const output = sessions.get('session-bounded')?.outputBuffer ?? '';
|
|
53
|
+
expect(output.length).toBeLessThanOrEqual(64 * 1024);
|
|
54
|
+
expect(output).toContain('AgentsWorkStudio: omitted');
|
|
55
|
+
expect(output.endsWith('a')).toBe(true);
|
|
50
56
|
});
|
|
51
57
|
it('increments sequence correctly', () => {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
expect(
|
|
58
|
+
const session = createSession();
|
|
59
|
+
sessions.set('session-1', session);
|
|
60
|
+
session.seq += 1;
|
|
61
|
+
expect(session.seq).toBe(1);
|
|
62
|
+
session.seq += 1;
|
|
63
|
+
expect(session.seq).toBe(2);
|
|
56
64
|
});
|
|
57
65
|
it('builds correct status payload', () => {
|
|
58
66
|
const buildStatusPayload = (agentId, sessionId, status) => ({
|
|
59
|
-
agentId,
|
|
67
|
+
agentId,
|
|
68
|
+
sessionId,
|
|
69
|
+
status,
|
|
60
70
|
});
|
|
61
71
|
expect(buildStatusPayload('agent-1', 'session-1', 'running')).toEqual({
|
|
62
|
-
agentId: 'agent-1',
|
|
72
|
+
agentId: 'agent-1',
|
|
73
|
+
sessionId: 'session-1',
|
|
74
|
+
status: 'running',
|
|
63
75
|
});
|
|
64
76
|
expect(buildStatusPayload('agent-1', null, 'stopped')).toEqual({
|
|
65
|
-
agentId: 'agent-1',
|
|
77
|
+
agentId: 'agent-1',
|
|
78
|
+
sessionId: null,
|
|
79
|
+
status: 'stopped',
|
|
66
80
|
});
|
|
67
81
|
});
|
|
68
82
|
});
|