@wingman-ai/gateway 0.2.2 → 0.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.
- package/.wingman/agents/README.md +7 -1
- package/.wingman/agents/coding/agent.md +299 -201
- package/.wingman/agents/coding-v2/agent.md +127 -0
- package/.wingman/agents/coding-v2/implementor.md +89 -0
- package/.wingman/agents/main/agent.md +4 -0
- package/README.md +1 -0
- package/dist/agent/config/agentConfig.cjs +31 -17
- package/dist/agent/config/agentConfig.d.ts +23 -1
- package/dist/agent/config/agentConfig.js +30 -19
- package/dist/agent/config/agentLoader.cjs +26 -8
- package/dist/agent/config/agentLoader.d.ts +4 -2
- package/dist/agent/config/agentLoader.js +26 -8
- package/dist/agent/config/modelFactory.cjs +95 -25
- package/dist/agent/config/modelFactory.d.ts +13 -1
- package/dist/agent/config/modelFactory.js +95 -25
- package/dist/agent/config/toolRegistry.cjs +19 -6
- package/dist/agent/config/toolRegistry.d.ts +5 -2
- package/dist/agent/config/toolRegistry.js +19 -6
- package/dist/agent/middleware/hooks/types.cjs +13 -13
- package/dist/agent/middleware/hooks/types.d.ts +1 -1
- package/dist/agent/middleware/hooks/types.js +14 -14
- package/dist/agent/tests/agentConfig.test.cjs +22 -2
- package/dist/agent/tests/agentConfig.test.js +22 -2
- package/dist/agent/tests/agentLoader.test.cjs +38 -1
- package/dist/agent/tests/agentLoader.test.js +38 -1
- package/dist/agent/tests/backgroundTerminal.test.cjs +70 -0
- package/dist/agent/tests/backgroundTerminal.test.d.ts +1 -0
- package/dist/agent/tests/backgroundTerminal.test.js +64 -0
- package/dist/agent/tests/commandExecuteTool.test.cjs +29 -0
- package/dist/agent/tests/commandExecuteTool.test.d.ts +1 -0
- package/dist/agent/tests/commandExecuteTool.test.js +23 -0
- package/dist/agent/tests/modelFactory.test.cjs +47 -5
- package/dist/agent/tests/modelFactory.test.js +47 -5
- package/dist/agent/tests/terminalSessionManager.test.cjs +121 -0
- package/dist/agent/tests/terminalSessionManager.test.d.ts +1 -0
- package/dist/agent/tests/terminalSessionManager.test.js +115 -0
- package/dist/agent/tests/toolRegistry.test.cjs +14 -2
- package/dist/agent/tests/toolRegistry.test.js +14 -2
- package/dist/agent/tools/background_terminal.cjs +128 -0
- package/dist/agent/tools/background_terminal.d.ts +41 -0
- package/dist/agent/tools/background_terminal.js +94 -0
- package/dist/agent/tools/code_search.cjs +6 -6
- package/dist/agent/tools/code_search.d.ts +1 -1
- package/dist/agent/tools/code_search.js +7 -7
- package/dist/agent/tools/command_execute.cjs +22 -7
- package/dist/agent/tools/command_execute.d.ts +3 -2
- package/dist/agent/tools/command_execute.js +23 -8
- package/dist/agent/tools/git_status.cjs +3 -3
- package/dist/agent/tools/git_status.d.ts +1 -1
- package/dist/agent/tools/git_status.js +4 -4
- package/dist/agent/tools/internet_search.cjs +6 -6
- package/dist/agent/tools/internet_search.d.ts +1 -1
- package/dist/agent/tools/internet_search.js +7 -7
- package/dist/agent/tools/terminal_session_manager.cjs +321 -0
- package/dist/agent/tools/terminal_session_manager.d.ts +77 -0
- package/dist/agent/tools/terminal_session_manager.js +284 -0
- package/dist/agent/tools/think.cjs +4 -4
- package/dist/agent/tools/think.d.ts +1 -1
- package/dist/agent/tools/think.js +5 -5
- package/dist/agent/tools/ui_registry.cjs +13 -13
- package/dist/agent/tools/ui_registry.d.ts +4 -4
- package/dist/agent/tools/ui_registry.js +14 -14
- package/dist/agent/tools/web_crawler.cjs +4 -4
- package/dist/agent/tools/web_crawler.d.ts +1 -1
- package/dist/agent/tools/web_crawler.js +5 -5
- package/dist/agent/utils.cjs +2 -1
- package/dist/agent/utils.js +2 -1
- package/dist/cli/commands/init.cjs +7 -6
- package/dist/cli/commands/init.js +7 -6
- package/dist/cli/commands/provider.cjs +17 -3
- package/dist/cli/commands/provider.js +17 -3
- package/dist/cli/config/loader.cjs +27 -0
- package/dist/cli/config/loader.js +27 -0
- package/dist/cli/config/schema.cjs +146 -68
- package/dist/cli/config/schema.d.ts +89 -1
- package/dist/cli/config/schema.js +134 -68
- package/dist/cli/core/agentInvoker.cjs +344 -17
- package/dist/cli/core/agentInvoker.d.ts +63 -3
- package/dist/cli/core/agentInvoker.js +303 -12
- package/dist/cli/core/sessionManager.cjs +32 -5
- package/dist/cli/core/sessionManager.js +32 -5
- package/dist/cli/core/streamParser.cjs +15 -0
- package/dist/cli/core/streamParser.js +15 -0
- package/dist/cli/index.cjs +6 -5
- package/dist/cli/index.js +6 -5
- package/dist/cli/types.d.ts +32 -0
- package/dist/cli/ui/toolDisplayHelpers.cjs +2 -0
- package/dist/cli/ui/toolDisplayHelpers.js +2 -0
- package/dist/gateway/hooks/registry.cjs +2 -1
- package/dist/gateway/hooks/registry.d.ts +1 -1
- package/dist/gateway/hooks/registry.js +2 -1
- package/dist/gateway/hooks/types.cjs +11 -11
- package/dist/gateway/hooks/types.d.ts +1 -1
- package/dist/gateway/hooks/types.js +12 -12
- package/dist/gateway/http/agents.cjs +67 -4
- package/dist/gateway/http/agents.js +67 -4
- package/dist/gateway/http/sessions.cjs +7 -7
- package/dist/gateway/http/sessions.js +7 -7
- package/dist/gateway/http/types.d.ts +5 -3
- package/dist/gateway/http/webhooks.cjs +6 -5
- package/dist/gateway/http/webhooks.js +6 -5
- package/dist/gateway/server.cjs +198 -41
- package/dist/gateway/server.d.ts +9 -1
- package/dist/gateway/server.js +198 -41
- package/dist/gateway/types.d.ts +1 -0
- package/dist/gateway/validation.cjs +39 -39
- package/dist/gateway/validation.d.ts +1 -1
- package/dist/gateway/validation.js +40 -40
- package/dist/providers/codex.cjs +167 -0
- package/dist/providers/codex.d.ts +15 -0
- package/dist/providers/codex.js +127 -0
- package/dist/providers/credentials.cjs +8 -0
- package/dist/providers/credentials.js +8 -0
- package/dist/providers/registry.cjs +11 -0
- package/dist/providers/registry.d.ts +1 -1
- package/dist/providers/registry.js +11 -0
- package/dist/tests/additionalMessageMiddleware.test.cjs +3 -0
- package/dist/tests/additionalMessageMiddleware.test.js +3 -0
- package/dist/tests/agentInvokerSummarization.test.cjs +455 -0
- package/dist/tests/agentInvokerSummarization.test.d.ts +1 -0
- package/dist/tests/agentInvokerSummarization.test.js +449 -0
- package/dist/tests/agents-api.test.cjs +45 -5
- package/dist/tests/agents-api.test.js +45 -5
- package/dist/tests/cli-config-loader.test.cjs +88 -0
- package/dist/tests/cli-config-loader.test.js +88 -0
- package/dist/tests/cli-init.test.cjs +27 -3
- package/dist/tests/cli-init.test.js +27 -3
- package/dist/tests/codex-credentials-precedence.test.cjs +94 -0
- package/dist/tests/codex-credentials-precedence.test.d.ts +1 -0
- package/dist/tests/codex-credentials-precedence.test.js +88 -0
- package/dist/tests/codex-provider.test.cjs +210 -0
- package/dist/tests/codex-provider.test.d.ts +1 -0
- package/dist/tests/codex-provider.test.js +204 -0
- package/dist/tests/gateway.test.cjs +115 -8
- package/dist/tests/gateway.test.js +115 -8
- package/dist/tests/provider-command-codex.test.cjs +57 -0
- package/dist/tests/provider-command-codex.test.d.ts +1 -0
- package/dist/tests/provider-command-codex.test.js +51 -0
- package/dist/tests/sessionStateMessages.test.cjs +38 -0
- package/dist/tests/sessionStateMessages.test.js +38 -0
- package/dist/tests/toolDisplayHelpers.test.cjs +3 -0
- package/dist/tests/toolDisplayHelpers.test.js +3 -0
- package/dist/tools/mcp-finance.cjs +48 -48
- package/dist/tools/mcp-finance.js +48 -48
- package/dist/types/mcp.cjs +15 -15
- package/dist/types/mcp.d.ts +1 -1
- package/dist/types/mcp.js +16 -16
- package/dist/types/voice.cjs +21 -21
- package/dist/types/voice.d.ts +1 -1
- package/dist/types/voice.js +22 -22
- package/dist/webui/assets/index-DVWQluit.css +11 -0
- package/dist/webui/assets/index-Dlyzwalc.js +270 -0
- package/dist/webui/favicon-32x32.png +0 -0
- package/dist/webui/favicon-64x64.png +0 -0
- package/dist/webui/favicon.webp +0 -0
- package/dist/webui/index.html +4 -2
- package/package.json +13 -12
- package/.wingman/agents/coding/implementor.md +0 -79
- package/dist/webui/assets/index-CPhfGPHc.js +0 -182
- package/dist/webui/assets/index-DDsMIOTX.css +0 -11
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __webpack_require__ = {};
|
|
3
|
+
(()=>{
|
|
4
|
+
__webpack_require__.d = (exports1, definition)=>{
|
|
5
|
+
for(var key in definition)if (__webpack_require__.o(definition, key) && !__webpack_require__.o(exports1, key)) Object.defineProperty(exports1, key, {
|
|
6
|
+
enumerable: true,
|
|
7
|
+
get: definition[key]
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
})();
|
|
11
|
+
(()=>{
|
|
12
|
+
__webpack_require__.o = (obj, prop)=>Object.prototype.hasOwnProperty.call(obj, prop);
|
|
13
|
+
})();
|
|
14
|
+
(()=>{
|
|
15
|
+
__webpack_require__.r = (exports1)=>{
|
|
16
|
+
if ("u" > typeof Symbol && Symbol.toStringTag) Object.defineProperty(exports1, Symbol.toStringTag, {
|
|
17
|
+
value: 'Module'
|
|
18
|
+
});
|
|
19
|
+
Object.defineProperty(exports1, '__esModule', {
|
|
20
|
+
value: true
|
|
21
|
+
});
|
|
22
|
+
};
|
|
23
|
+
})();
|
|
24
|
+
var __webpack_exports__ = {};
|
|
25
|
+
__webpack_require__.r(__webpack_exports__);
|
|
26
|
+
__webpack_require__.d(__webpack_exports__, {
|
|
27
|
+
TerminalSessionManager: ()=>TerminalSessionManager,
|
|
28
|
+
getSharedTerminalSessionManager: ()=>getSharedTerminalSessionManager
|
|
29
|
+
});
|
|
30
|
+
const external_node_child_process_namespaceObject = require("node:child_process");
|
|
31
|
+
const external_node_crypto_namespaceObject = require("node:crypto");
|
|
32
|
+
const external_node_events_namespaceObject = require("node:events");
|
|
33
|
+
function _define_property(obj, key, value) {
|
|
34
|
+
if (key in obj) Object.defineProperty(obj, key, {
|
|
35
|
+
value: value,
|
|
36
|
+
enumerable: true,
|
|
37
|
+
configurable: true,
|
|
38
|
+
writable: true
|
|
39
|
+
});
|
|
40
|
+
else obj[key] = value;
|
|
41
|
+
return obj;
|
|
42
|
+
}
|
|
43
|
+
const DEFAULT_MAX_SESSIONS_PER_OWNER = 4;
|
|
44
|
+
const DEFAULT_MAX_BUFFERED_CHARS = 256000;
|
|
45
|
+
const DEFAULT_MAX_RUNTIME_MS = 1800000;
|
|
46
|
+
const DEFAULT_IDLE_TIMEOUT_MS = 900000;
|
|
47
|
+
const DEFAULT_COMPLETED_RETENTION_MS = 900000;
|
|
48
|
+
const DEFAULT_TERMINATION_GRACE_MS = 2000;
|
|
49
|
+
const DEFAULT_POLL_WAIT_MS = 1000;
|
|
50
|
+
const DEFAULT_MAX_POLL_OUTPUT_CHARS = 8000;
|
|
51
|
+
class TerminalSessionManager {
|
|
52
|
+
dispose() {
|
|
53
|
+
clearInterval(this.cleanupTimer);
|
|
54
|
+
for (const record of this.sessions.values()){
|
|
55
|
+
if (record.runtimeTimer) clearTimeout(record.runtimeTimer);
|
|
56
|
+
if ("running" === record.status) this.safeKill(record.process, "SIGKILL");
|
|
57
|
+
record.emitter.removeAllListeners();
|
|
58
|
+
}
|
|
59
|
+
this.sessions.clear();
|
|
60
|
+
this.ownerIndex.clear();
|
|
61
|
+
}
|
|
62
|
+
startSession(input) {
|
|
63
|
+
const ownerId = this.normalizeOwnerId(input.ownerId);
|
|
64
|
+
const existing = this.ownerIndex.get(ownerId);
|
|
65
|
+
if ((existing?.size || 0) >= this.maxSessionsPerOwner) throw new Error(`Owner "${ownerId}" reached terminal session limit (${this.maxSessionsPerOwner})`);
|
|
66
|
+
const runtimeLimitMs = Math.max(1000, input.runtimeLimitMs ?? this.maxRuntimeMs);
|
|
67
|
+
const process = (0, external_node_child_process_namespaceObject.spawn)(input.command, [], {
|
|
68
|
+
cwd: input.cwd,
|
|
69
|
+
env: input.env,
|
|
70
|
+
shell: true,
|
|
71
|
+
stdio: [
|
|
72
|
+
"pipe",
|
|
73
|
+
"pipe",
|
|
74
|
+
"pipe"
|
|
75
|
+
],
|
|
76
|
+
windowsHide: true
|
|
77
|
+
});
|
|
78
|
+
const now = Date.now();
|
|
79
|
+
const sessionId = (0, external_node_crypto_namespaceObject.randomUUID)();
|
|
80
|
+
const record = {
|
|
81
|
+
sessionId,
|
|
82
|
+
ownerId,
|
|
83
|
+
command: input.command,
|
|
84
|
+
cwd: input.cwd,
|
|
85
|
+
process,
|
|
86
|
+
status: "running",
|
|
87
|
+
startedAt: now,
|
|
88
|
+
updatedAt: now,
|
|
89
|
+
output: "",
|
|
90
|
+
cursor: 0,
|
|
91
|
+
droppedChars: 0,
|
|
92
|
+
killRequested: false,
|
|
93
|
+
runtimeLimitMs,
|
|
94
|
+
runtimeTimer: null,
|
|
95
|
+
emitter: new external_node_events_namespaceObject.EventEmitter()
|
|
96
|
+
};
|
|
97
|
+
record.runtimeTimer = setTimeout(()=>{
|
|
98
|
+
if ("running" !== record.status) return;
|
|
99
|
+
record.status = "timed_out";
|
|
100
|
+
record.killRequested = true;
|
|
101
|
+
this.appendOutput(record, `\n[terminal] Session timed out after ${Math.floor(runtimeLimitMs / 1000)}s\n`);
|
|
102
|
+
this.safeKill(record.process, "SIGTERM");
|
|
103
|
+
setTimeout(()=>{
|
|
104
|
+
if ("running" === record.status || !record.finishedAt) this.safeKill(record.process, "SIGKILL");
|
|
105
|
+
}, this.terminationGraceMs).unref?.();
|
|
106
|
+
}, runtimeLimitMs);
|
|
107
|
+
record.runtimeTimer.unref?.();
|
|
108
|
+
process.stdout?.on("data", (chunk)=>{
|
|
109
|
+
this.appendOutput(record, chunk.toString());
|
|
110
|
+
});
|
|
111
|
+
process.stderr?.on("data", (chunk)=>{
|
|
112
|
+
this.appendOutput(record, chunk.toString());
|
|
113
|
+
});
|
|
114
|
+
process.on("error", (error)=>{
|
|
115
|
+
if ("running" === record.status) record.status = "error";
|
|
116
|
+
this.appendOutput(record, `\n[terminal:error] ${error.message}\n`);
|
|
117
|
+
this.finalizeRecord(record, null, null);
|
|
118
|
+
});
|
|
119
|
+
process.on("exit", (code, signal)=>{
|
|
120
|
+
if ("running" === record.status) if (record.killRequested || signal) record.status = "killed";
|
|
121
|
+
else if ("number" == typeof code && 0 !== code) record.status = "error";
|
|
122
|
+
else record.status = "completed";
|
|
123
|
+
this.finalizeRecord(record, code, signal);
|
|
124
|
+
});
|
|
125
|
+
this.sessions.set(sessionId, record);
|
|
126
|
+
if (existing) existing.add(sessionId);
|
|
127
|
+
else this.ownerIndex.set(ownerId, new Set([
|
|
128
|
+
sessionId
|
|
129
|
+
]));
|
|
130
|
+
return this.toSnapshot(record);
|
|
131
|
+
}
|
|
132
|
+
async pollSession(input) {
|
|
133
|
+
const record = this.getOwnedRecord(input.ownerId, input.sessionId);
|
|
134
|
+
const waitMs = Math.max(0, input.waitMs ?? DEFAULT_POLL_WAIT_MS);
|
|
135
|
+
const maxOutputChars = Math.max(1, input.maxOutputChars ?? DEFAULT_MAX_POLL_OUTPUT_CHARS);
|
|
136
|
+
if (waitMs > 0 && "running" === record.status && record.cursor >= record.output.length) await this.waitForUpdate(record, waitMs);
|
|
137
|
+
const available = Math.max(0, record.output.length - record.cursor);
|
|
138
|
+
const readChars = Math.min(maxOutputChars, available);
|
|
139
|
+
const output = readChars > 0 ? record.output.slice(record.cursor, record.cursor + readChars) : "";
|
|
140
|
+
record.cursor += readChars;
|
|
141
|
+
return {
|
|
142
|
+
...this.toSnapshot(record),
|
|
143
|
+
output,
|
|
144
|
+
hasMore: record.cursor < record.output.length
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
writeSession(input) {
|
|
148
|
+
const record = this.getOwnedRecord(input.ownerId, input.sessionId);
|
|
149
|
+
if ("running" !== record.status) throw new Error(`Terminal session ${record.sessionId} is not running`);
|
|
150
|
+
if (!record.process.stdin || record.process.stdin.destroyed) throw new Error(`Terminal session ${record.sessionId} has no writable stdin`);
|
|
151
|
+
record.process.stdin.write(input.chars);
|
|
152
|
+
record.updatedAt = Date.now();
|
|
153
|
+
record.emitter.emit("update");
|
|
154
|
+
return this.toSnapshot(record);
|
|
155
|
+
}
|
|
156
|
+
killSession(input) {
|
|
157
|
+
const record = this.getOwnedRecord(input.ownerId, input.sessionId);
|
|
158
|
+
if ("running" !== record.status) return this.toSnapshot(record);
|
|
159
|
+
const signal = input.signal || "SIGTERM";
|
|
160
|
+
record.killRequested = true;
|
|
161
|
+
this.safeKill(record.process, signal);
|
|
162
|
+
if ("SIGKILL" !== signal) setTimeout(()=>{
|
|
163
|
+
if ("running" === record.status) {
|
|
164
|
+
record.killRequested = true;
|
|
165
|
+
this.safeKill(record.process, "SIGKILL");
|
|
166
|
+
}
|
|
167
|
+
}, this.terminationGraceMs).unref?.();
|
|
168
|
+
return this.toSnapshot(record);
|
|
169
|
+
}
|
|
170
|
+
listSessions(ownerId) {
|
|
171
|
+
const normalizedOwnerId = this.normalizeOwnerId(ownerId);
|
|
172
|
+
const sessionIds = this.ownerIndex.get(normalizedOwnerId);
|
|
173
|
+
if (!sessionIds || 0 === sessionIds.size) return [];
|
|
174
|
+
return [
|
|
175
|
+
...sessionIds
|
|
176
|
+
].map((sessionId)=>this.sessions.get(sessionId)).filter((entry)=>Boolean(entry)).sort((a, b)=>b.startedAt - a.startedAt).map((entry)=>this.toSnapshot(entry));
|
|
177
|
+
}
|
|
178
|
+
waitForUpdate(record, waitMs) {
|
|
179
|
+
return new Promise((resolve)=>{
|
|
180
|
+
let resolved = false;
|
|
181
|
+
const onUpdate = ()=>{
|
|
182
|
+
if (resolved) return;
|
|
183
|
+
resolved = true;
|
|
184
|
+
clearTimeout(timer);
|
|
185
|
+
record.emitter.off("update", onUpdate);
|
|
186
|
+
resolve();
|
|
187
|
+
};
|
|
188
|
+
const timer = setTimeout(()=>{
|
|
189
|
+
if (resolved) return;
|
|
190
|
+
resolved = true;
|
|
191
|
+
record.emitter.off("update", onUpdate);
|
|
192
|
+
resolve();
|
|
193
|
+
}, waitMs);
|
|
194
|
+
timer.unref?.();
|
|
195
|
+
record.emitter.on("update", onUpdate);
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
appendOutput(record, chunk) {
|
|
199
|
+
if (!chunk) return;
|
|
200
|
+
record.output += chunk;
|
|
201
|
+
if (record.output.length > this.maxBufferedCharsPerSession) {
|
|
202
|
+
const overflow = record.output.length - this.maxBufferedCharsPerSession;
|
|
203
|
+
record.output = record.output.slice(overflow);
|
|
204
|
+
record.droppedChars += overflow;
|
|
205
|
+
record.cursor = Math.max(0, record.cursor - overflow);
|
|
206
|
+
}
|
|
207
|
+
record.updatedAt = Date.now();
|
|
208
|
+
record.emitter.emit("update");
|
|
209
|
+
}
|
|
210
|
+
finalizeRecord(record, code, signal) {
|
|
211
|
+
record.exitCode = code;
|
|
212
|
+
record.signal = signal;
|
|
213
|
+
record.finishedAt = Date.now();
|
|
214
|
+
record.updatedAt = record.finishedAt;
|
|
215
|
+
if (record.runtimeTimer) {
|
|
216
|
+
clearTimeout(record.runtimeTimer);
|
|
217
|
+
record.runtimeTimer = null;
|
|
218
|
+
}
|
|
219
|
+
record.emitter.emit("update");
|
|
220
|
+
}
|
|
221
|
+
cleanupExpiredSessions() {
|
|
222
|
+
const now = Date.now();
|
|
223
|
+
for (const record of this.sessions.values()){
|
|
224
|
+
if ("running" === record.status) {
|
|
225
|
+
const idleForMs = now - record.updatedAt;
|
|
226
|
+
if (idleForMs > this.idleTimeoutMs) {
|
|
227
|
+
record.status = "timed_out";
|
|
228
|
+
record.killRequested = true;
|
|
229
|
+
this.appendOutput(record, `\n[terminal] Session idle timeout after ${Math.floor(this.idleTimeoutMs / 1000)}s\n`);
|
|
230
|
+
this.safeKill(record.process, "SIGTERM");
|
|
231
|
+
setTimeout(()=>{
|
|
232
|
+
if ("running" === record.status) this.safeKill(record.process, "SIGKILL");
|
|
233
|
+
}, this.terminationGraceMs).unref?.();
|
|
234
|
+
}
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
if (record.finishedAt && now - record.finishedAt > this.completedSessionRetentionMs) this.deleteSession(record.sessionId);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
deleteSession(sessionId) {
|
|
241
|
+
const record = this.sessions.get(sessionId);
|
|
242
|
+
if (!record) return;
|
|
243
|
+
if (record.runtimeTimer) {
|
|
244
|
+
clearTimeout(record.runtimeTimer);
|
|
245
|
+
record.runtimeTimer = null;
|
|
246
|
+
}
|
|
247
|
+
record.emitter.removeAllListeners();
|
|
248
|
+
this.sessions.delete(sessionId);
|
|
249
|
+
const ownerSessions = this.ownerIndex.get(record.ownerId);
|
|
250
|
+
if (!ownerSessions) return;
|
|
251
|
+
ownerSessions.delete(sessionId);
|
|
252
|
+
if (0 === ownerSessions.size) this.ownerIndex.delete(record.ownerId);
|
|
253
|
+
}
|
|
254
|
+
getOwnedRecord(ownerId, sessionId) {
|
|
255
|
+
const normalizedOwnerId = this.normalizeOwnerId(ownerId);
|
|
256
|
+
const record = this.sessions.get(sessionId);
|
|
257
|
+
if (!record) throw new Error(`Terminal session ${sessionId} was not found`);
|
|
258
|
+
if (record.ownerId !== normalizedOwnerId) throw new Error(`Terminal session ${sessionId} is not accessible`);
|
|
259
|
+
return record;
|
|
260
|
+
}
|
|
261
|
+
toSnapshot(record) {
|
|
262
|
+
return {
|
|
263
|
+
sessionId: record.sessionId,
|
|
264
|
+
ownerId: record.ownerId,
|
|
265
|
+
command: record.command,
|
|
266
|
+
cwd: record.cwd,
|
|
267
|
+
status: record.status,
|
|
268
|
+
startedAt: record.startedAt,
|
|
269
|
+
updatedAt: record.updatedAt,
|
|
270
|
+
finishedAt: record.finishedAt,
|
|
271
|
+
exitCode: record.exitCode,
|
|
272
|
+
signal: record.signal,
|
|
273
|
+
droppedChars: record.droppedChars
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
normalizeOwnerId(ownerId) {
|
|
277
|
+
const trimmed = ownerId.trim();
|
|
278
|
+
if (!trimmed) throw new Error("Terminal ownerId is required");
|
|
279
|
+
return trimmed;
|
|
280
|
+
}
|
|
281
|
+
safeKill(process, signal) {
|
|
282
|
+
try {
|
|
283
|
+
process.kill(signal);
|
|
284
|
+
} catch {}
|
|
285
|
+
}
|
|
286
|
+
constructor(options = {}){
|
|
287
|
+
_define_property(this, "sessions", new Map());
|
|
288
|
+
_define_property(this, "ownerIndex", new Map());
|
|
289
|
+
_define_property(this, "cleanupTimer", void 0);
|
|
290
|
+
_define_property(this, "maxSessionsPerOwner", void 0);
|
|
291
|
+
_define_property(this, "maxBufferedCharsPerSession", void 0);
|
|
292
|
+
_define_property(this, "maxRuntimeMs", void 0);
|
|
293
|
+
_define_property(this, "idleTimeoutMs", void 0);
|
|
294
|
+
_define_property(this, "completedSessionRetentionMs", void 0);
|
|
295
|
+
_define_property(this, "terminationGraceMs", void 0);
|
|
296
|
+
this.maxSessionsPerOwner = options.maxSessionsPerOwner ?? DEFAULT_MAX_SESSIONS_PER_OWNER;
|
|
297
|
+
this.maxBufferedCharsPerSession = options.maxBufferedCharsPerSession ?? DEFAULT_MAX_BUFFERED_CHARS;
|
|
298
|
+
this.maxRuntimeMs = options.maxRuntimeMs ?? DEFAULT_MAX_RUNTIME_MS;
|
|
299
|
+
this.idleTimeoutMs = options.idleTimeoutMs ?? DEFAULT_IDLE_TIMEOUT_MS;
|
|
300
|
+
this.completedSessionRetentionMs = options.completedSessionRetentionMs ?? DEFAULT_COMPLETED_RETENTION_MS;
|
|
301
|
+
this.terminationGraceMs = options.terminationGraceMs ?? DEFAULT_TERMINATION_GRACE_MS;
|
|
302
|
+
this.cleanupTimer = setInterval(()=>{
|
|
303
|
+
this.cleanupExpiredSessions();
|
|
304
|
+
}, 60000);
|
|
305
|
+
this.cleanupTimer.unref?.();
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
let sharedTerminalSessionManager = null;
|
|
309
|
+
const getSharedTerminalSessionManager = ()=>{
|
|
310
|
+
if (!sharedTerminalSessionManager) sharedTerminalSessionManager = new TerminalSessionManager();
|
|
311
|
+
return sharedTerminalSessionManager;
|
|
312
|
+
};
|
|
313
|
+
exports.TerminalSessionManager = __webpack_exports__.TerminalSessionManager;
|
|
314
|
+
exports.getSharedTerminalSessionManager = __webpack_exports__.getSharedTerminalSessionManager;
|
|
315
|
+
for(var __rspack_i in __webpack_exports__)if (-1 === [
|
|
316
|
+
"TerminalSessionManager",
|
|
317
|
+
"getSharedTerminalSessionManager"
|
|
318
|
+
].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
|
|
319
|
+
Object.defineProperty(exports, '__esModule', {
|
|
320
|
+
value: true
|
|
321
|
+
});
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
export type TerminalSessionStatus = "running" | "completed" | "error" | "killed" | "timed_out";
|
|
2
|
+
export interface TerminalSessionSnapshot {
|
|
3
|
+
sessionId: string;
|
|
4
|
+
ownerId: string;
|
|
5
|
+
command: string;
|
|
6
|
+
cwd: string;
|
|
7
|
+
status: TerminalSessionStatus;
|
|
8
|
+
startedAt: number;
|
|
9
|
+
updatedAt: number;
|
|
10
|
+
finishedAt?: number;
|
|
11
|
+
exitCode?: number | null;
|
|
12
|
+
signal?: NodeJS.Signals | null;
|
|
13
|
+
droppedChars: number;
|
|
14
|
+
}
|
|
15
|
+
export interface TerminalPollResult extends TerminalSessionSnapshot {
|
|
16
|
+
output: string;
|
|
17
|
+
hasMore: boolean;
|
|
18
|
+
}
|
|
19
|
+
export interface TerminalSessionManagerOptions {
|
|
20
|
+
maxSessionsPerOwner?: number;
|
|
21
|
+
maxBufferedCharsPerSession?: number;
|
|
22
|
+
maxRuntimeMs?: number;
|
|
23
|
+
idleTimeoutMs?: number;
|
|
24
|
+
completedSessionRetentionMs?: number;
|
|
25
|
+
terminationGraceMs?: number;
|
|
26
|
+
}
|
|
27
|
+
export interface StartTerminalSessionInput {
|
|
28
|
+
ownerId: string;
|
|
29
|
+
command: string;
|
|
30
|
+
cwd: string;
|
|
31
|
+
env: Record<string, string>;
|
|
32
|
+
runtimeLimitMs?: number;
|
|
33
|
+
}
|
|
34
|
+
export interface PollTerminalSessionInput {
|
|
35
|
+
ownerId: string;
|
|
36
|
+
sessionId: string;
|
|
37
|
+
waitMs?: number;
|
|
38
|
+
maxOutputChars?: number;
|
|
39
|
+
}
|
|
40
|
+
export interface WriteTerminalSessionInput {
|
|
41
|
+
ownerId: string;
|
|
42
|
+
sessionId: string;
|
|
43
|
+
chars: string;
|
|
44
|
+
}
|
|
45
|
+
export interface KillTerminalSessionInput {
|
|
46
|
+
ownerId: string;
|
|
47
|
+
sessionId: string;
|
|
48
|
+
signal?: NodeJS.Signals;
|
|
49
|
+
}
|
|
50
|
+
export declare class TerminalSessionManager {
|
|
51
|
+
private readonly sessions;
|
|
52
|
+
private readonly ownerIndex;
|
|
53
|
+
private readonly cleanupTimer;
|
|
54
|
+
private readonly maxSessionsPerOwner;
|
|
55
|
+
private readonly maxBufferedCharsPerSession;
|
|
56
|
+
private readonly maxRuntimeMs;
|
|
57
|
+
private readonly idleTimeoutMs;
|
|
58
|
+
private readonly completedSessionRetentionMs;
|
|
59
|
+
private readonly terminationGraceMs;
|
|
60
|
+
constructor(options?: TerminalSessionManagerOptions);
|
|
61
|
+
dispose(): void;
|
|
62
|
+
startSession(input: StartTerminalSessionInput): TerminalSessionSnapshot;
|
|
63
|
+
pollSession(input: PollTerminalSessionInput): Promise<TerminalPollResult>;
|
|
64
|
+
writeSession(input: WriteTerminalSessionInput): TerminalSessionSnapshot;
|
|
65
|
+
killSession(input: KillTerminalSessionInput): TerminalSessionSnapshot;
|
|
66
|
+
listSessions(ownerId: string): TerminalSessionSnapshot[];
|
|
67
|
+
private waitForUpdate;
|
|
68
|
+
private appendOutput;
|
|
69
|
+
private finalizeRecord;
|
|
70
|
+
private cleanupExpiredSessions;
|
|
71
|
+
private deleteSession;
|
|
72
|
+
private getOwnedRecord;
|
|
73
|
+
private toSnapshot;
|
|
74
|
+
private normalizeOwnerId;
|
|
75
|
+
private safeKill;
|
|
76
|
+
}
|
|
77
|
+
export declare const getSharedTerminalSessionManager: () => TerminalSessionManager;
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
import { EventEmitter } from "node:events";
|
|
4
|
+
function _define_property(obj, key, value) {
|
|
5
|
+
if (key in obj) Object.defineProperty(obj, key, {
|
|
6
|
+
value: value,
|
|
7
|
+
enumerable: true,
|
|
8
|
+
configurable: true,
|
|
9
|
+
writable: true
|
|
10
|
+
});
|
|
11
|
+
else obj[key] = value;
|
|
12
|
+
return obj;
|
|
13
|
+
}
|
|
14
|
+
const DEFAULT_MAX_SESSIONS_PER_OWNER = 4;
|
|
15
|
+
const DEFAULT_MAX_BUFFERED_CHARS = 256000;
|
|
16
|
+
const DEFAULT_MAX_RUNTIME_MS = 1800000;
|
|
17
|
+
const DEFAULT_IDLE_TIMEOUT_MS = 900000;
|
|
18
|
+
const DEFAULT_COMPLETED_RETENTION_MS = 900000;
|
|
19
|
+
const DEFAULT_TERMINATION_GRACE_MS = 2000;
|
|
20
|
+
const DEFAULT_POLL_WAIT_MS = 1000;
|
|
21
|
+
const DEFAULT_MAX_POLL_OUTPUT_CHARS = 8000;
|
|
22
|
+
class TerminalSessionManager {
|
|
23
|
+
dispose() {
|
|
24
|
+
clearInterval(this.cleanupTimer);
|
|
25
|
+
for (const record of this.sessions.values()){
|
|
26
|
+
if (record.runtimeTimer) clearTimeout(record.runtimeTimer);
|
|
27
|
+
if ("running" === record.status) this.safeKill(record.process, "SIGKILL");
|
|
28
|
+
record.emitter.removeAllListeners();
|
|
29
|
+
}
|
|
30
|
+
this.sessions.clear();
|
|
31
|
+
this.ownerIndex.clear();
|
|
32
|
+
}
|
|
33
|
+
startSession(input) {
|
|
34
|
+
const ownerId = this.normalizeOwnerId(input.ownerId);
|
|
35
|
+
const existing = this.ownerIndex.get(ownerId);
|
|
36
|
+
if ((existing?.size || 0) >= this.maxSessionsPerOwner) throw new Error(`Owner "${ownerId}" reached terminal session limit (${this.maxSessionsPerOwner})`);
|
|
37
|
+
const runtimeLimitMs = Math.max(1000, input.runtimeLimitMs ?? this.maxRuntimeMs);
|
|
38
|
+
const process = spawn(input.command, [], {
|
|
39
|
+
cwd: input.cwd,
|
|
40
|
+
env: input.env,
|
|
41
|
+
shell: true,
|
|
42
|
+
stdio: [
|
|
43
|
+
"pipe",
|
|
44
|
+
"pipe",
|
|
45
|
+
"pipe"
|
|
46
|
+
],
|
|
47
|
+
windowsHide: true
|
|
48
|
+
});
|
|
49
|
+
const now = Date.now();
|
|
50
|
+
const sessionId = randomUUID();
|
|
51
|
+
const record = {
|
|
52
|
+
sessionId,
|
|
53
|
+
ownerId,
|
|
54
|
+
command: input.command,
|
|
55
|
+
cwd: input.cwd,
|
|
56
|
+
process,
|
|
57
|
+
status: "running",
|
|
58
|
+
startedAt: now,
|
|
59
|
+
updatedAt: now,
|
|
60
|
+
output: "",
|
|
61
|
+
cursor: 0,
|
|
62
|
+
droppedChars: 0,
|
|
63
|
+
killRequested: false,
|
|
64
|
+
runtimeLimitMs,
|
|
65
|
+
runtimeTimer: null,
|
|
66
|
+
emitter: new EventEmitter()
|
|
67
|
+
};
|
|
68
|
+
record.runtimeTimer = setTimeout(()=>{
|
|
69
|
+
if ("running" !== record.status) return;
|
|
70
|
+
record.status = "timed_out";
|
|
71
|
+
record.killRequested = true;
|
|
72
|
+
this.appendOutput(record, `\n[terminal] Session timed out after ${Math.floor(runtimeLimitMs / 1000)}s\n`);
|
|
73
|
+
this.safeKill(record.process, "SIGTERM");
|
|
74
|
+
setTimeout(()=>{
|
|
75
|
+
if ("running" === record.status || !record.finishedAt) this.safeKill(record.process, "SIGKILL");
|
|
76
|
+
}, this.terminationGraceMs).unref?.();
|
|
77
|
+
}, runtimeLimitMs);
|
|
78
|
+
record.runtimeTimer.unref?.();
|
|
79
|
+
process.stdout?.on("data", (chunk)=>{
|
|
80
|
+
this.appendOutput(record, chunk.toString());
|
|
81
|
+
});
|
|
82
|
+
process.stderr?.on("data", (chunk)=>{
|
|
83
|
+
this.appendOutput(record, chunk.toString());
|
|
84
|
+
});
|
|
85
|
+
process.on("error", (error)=>{
|
|
86
|
+
if ("running" === record.status) record.status = "error";
|
|
87
|
+
this.appendOutput(record, `\n[terminal:error] ${error.message}\n`);
|
|
88
|
+
this.finalizeRecord(record, null, null);
|
|
89
|
+
});
|
|
90
|
+
process.on("exit", (code, signal)=>{
|
|
91
|
+
if ("running" === record.status) if (record.killRequested || signal) record.status = "killed";
|
|
92
|
+
else if ("number" == typeof code && 0 !== code) record.status = "error";
|
|
93
|
+
else record.status = "completed";
|
|
94
|
+
this.finalizeRecord(record, code, signal);
|
|
95
|
+
});
|
|
96
|
+
this.sessions.set(sessionId, record);
|
|
97
|
+
if (existing) existing.add(sessionId);
|
|
98
|
+
else this.ownerIndex.set(ownerId, new Set([
|
|
99
|
+
sessionId
|
|
100
|
+
]));
|
|
101
|
+
return this.toSnapshot(record);
|
|
102
|
+
}
|
|
103
|
+
async pollSession(input) {
|
|
104
|
+
const record = this.getOwnedRecord(input.ownerId, input.sessionId);
|
|
105
|
+
const waitMs = Math.max(0, input.waitMs ?? DEFAULT_POLL_WAIT_MS);
|
|
106
|
+
const maxOutputChars = Math.max(1, input.maxOutputChars ?? DEFAULT_MAX_POLL_OUTPUT_CHARS);
|
|
107
|
+
if (waitMs > 0 && "running" === record.status && record.cursor >= record.output.length) await this.waitForUpdate(record, waitMs);
|
|
108
|
+
const available = Math.max(0, record.output.length - record.cursor);
|
|
109
|
+
const readChars = Math.min(maxOutputChars, available);
|
|
110
|
+
const output = readChars > 0 ? record.output.slice(record.cursor, record.cursor + readChars) : "";
|
|
111
|
+
record.cursor += readChars;
|
|
112
|
+
return {
|
|
113
|
+
...this.toSnapshot(record),
|
|
114
|
+
output,
|
|
115
|
+
hasMore: record.cursor < record.output.length
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
writeSession(input) {
|
|
119
|
+
const record = this.getOwnedRecord(input.ownerId, input.sessionId);
|
|
120
|
+
if ("running" !== record.status) throw new Error(`Terminal session ${record.sessionId} is not running`);
|
|
121
|
+
if (!record.process.stdin || record.process.stdin.destroyed) throw new Error(`Terminal session ${record.sessionId} has no writable stdin`);
|
|
122
|
+
record.process.stdin.write(input.chars);
|
|
123
|
+
record.updatedAt = Date.now();
|
|
124
|
+
record.emitter.emit("update");
|
|
125
|
+
return this.toSnapshot(record);
|
|
126
|
+
}
|
|
127
|
+
killSession(input) {
|
|
128
|
+
const record = this.getOwnedRecord(input.ownerId, input.sessionId);
|
|
129
|
+
if ("running" !== record.status) return this.toSnapshot(record);
|
|
130
|
+
const signal = input.signal || "SIGTERM";
|
|
131
|
+
record.killRequested = true;
|
|
132
|
+
this.safeKill(record.process, signal);
|
|
133
|
+
if ("SIGKILL" !== signal) setTimeout(()=>{
|
|
134
|
+
if ("running" === record.status) {
|
|
135
|
+
record.killRequested = true;
|
|
136
|
+
this.safeKill(record.process, "SIGKILL");
|
|
137
|
+
}
|
|
138
|
+
}, this.terminationGraceMs).unref?.();
|
|
139
|
+
return this.toSnapshot(record);
|
|
140
|
+
}
|
|
141
|
+
listSessions(ownerId) {
|
|
142
|
+
const normalizedOwnerId = this.normalizeOwnerId(ownerId);
|
|
143
|
+
const sessionIds = this.ownerIndex.get(normalizedOwnerId);
|
|
144
|
+
if (!sessionIds || 0 === sessionIds.size) return [];
|
|
145
|
+
return [
|
|
146
|
+
...sessionIds
|
|
147
|
+
].map((sessionId)=>this.sessions.get(sessionId)).filter((entry)=>Boolean(entry)).sort((a, b)=>b.startedAt - a.startedAt).map((entry)=>this.toSnapshot(entry));
|
|
148
|
+
}
|
|
149
|
+
waitForUpdate(record, waitMs) {
|
|
150
|
+
return new Promise((resolve)=>{
|
|
151
|
+
let resolved = false;
|
|
152
|
+
const onUpdate = ()=>{
|
|
153
|
+
if (resolved) return;
|
|
154
|
+
resolved = true;
|
|
155
|
+
clearTimeout(timer);
|
|
156
|
+
record.emitter.off("update", onUpdate);
|
|
157
|
+
resolve();
|
|
158
|
+
};
|
|
159
|
+
const timer = setTimeout(()=>{
|
|
160
|
+
if (resolved) return;
|
|
161
|
+
resolved = true;
|
|
162
|
+
record.emitter.off("update", onUpdate);
|
|
163
|
+
resolve();
|
|
164
|
+
}, waitMs);
|
|
165
|
+
timer.unref?.();
|
|
166
|
+
record.emitter.on("update", onUpdate);
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
appendOutput(record, chunk) {
|
|
170
|
+
if (!chunk) return;
|
|
171
|
+
record.output += chunk;
|
|
172
|
+
if (record.output.length > this.maxBufferedCharsPerSession) {
|
|
173
|
+
const overflow = record.output.length - this.maxBufferedCharsPerSession;
|
|
174
|
+
record.output = record.output.slice(overflow);
|
|
175
|
+
record.droppedChars += overflow;
|
|
176
|
+
record.cursor = Math.max(0, record.cursor - overflow);
|
|
177
|
+
}
|
|
178
|
+
record.updatedAt = Date.now();
|
|
179
|
+
record.emitter.emit("update");
|
|
180
|
+
}
|
|
181
|
+
finalizeRecord(record, code, signal) {
|
|
182
|
+
record.exitCode = code;
|
|
183
|
+
record.signal = signal;
|
|
184
|
+
record.finishedAt = Date.now();
|
|
185
|
+
record.updatedAt = record.finishedAt;
|
|
186
|
+
if (record.runtimeTimer) {
|
|
187
|
+
clearTimeout(record.runtimeTimer);
|
|
188
|
+
record.runtimeTimer = null;
|
|
189
|
+
}
|
|
190
|
+
record.emitter.emit("update");
|
|
191
|
+
}
|
|
192
|
+
cleanupExpiredSessions() {
|
|
193
|
+
const now = Date.now();
|
|
194
|
+
for (const record of this.sessions.values()){
|
|
195
|
+
if ("running" === record.status) {
|
|
196
|
+
const idleForMs = now - record.updatedAt;
|
|
197
|
+
if (idleForMs > this.idleTimeoutMs) {
|
|
198
|
+
record.status = "timed_out";
|
|
199
|
+
record.killRequested = true;
|
|
200
|
+
this.appendOutput(record, `\n[terminal] Session idle timeout after ${Math.floor(this.idleTimeoutMs / 1000)}s\n`);
|
|
201
|
+
this.safeKill(record.process, "SIGTERM");
|
|
202
|
+
setTimeout(()=>{
|
|
203
|
+
if ("running" === record.status) this.safeKill(record.process, "SIGKILL");
|
|
204
|
+
}, this.terminationGraceMs).unref?.();
|
|
205
|
+
}
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
if (record.finishedAt && now - record.finishedAt > this.completedSessionRetentionMs) this.deleteSession(record.sessionId);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
deleteSession(sessionId) {
|
|
212
|
+
const record = this.sessions.get(sessionId);
|
|
213
|
+
if (!record) return;
|
|
214
|
+
if (record.runtimeTimer) {
|
|
215
|
+
clearTimeout(record.runtimeTimer);
|
|
216
|
+
record.runtimeTimer = null;
|
|
217
|
+
}
|
|
218
|
+
record.emitter.removeAllListeners();
|
|
219
|
+
this.sessions.delete(sessionId);
|
|
220
|
+
const ownerSessions = this.ownerIndex.get(record.ownerId);
|
|
221
|
+
if (!ownerSessions) return;
|
|
222
|
+
ownerSessions.delete(sessionId);
|
|
223
|
+
if (0 === ownerSessions.size) this.ownerIndex.delete(record.ownerId);
|
|
224
|
+
}
|
|
225
|
+
getOwnedRecord(ownerId, sessionId) {
|
|
226
|
+
const normalizedOwnerId = this.normalizeOwnerId(ownerId);
|
|
227
|
+
const record = this.sessions.get(sessionId);
|
|
228
|
+
if (!record) throw new Error(`Terminal session ${sessionId} was not found`);
|
|
229
|
+
if (record.ownerId !== normalizedOwnerId) throw new Error(`Terminal session ${sessionId} is not accessible`);
|
|
230
|
+
return record;
|
|
231
|
+
}
|
|
232
|
+
toSnapshot(record) {
|
|
233
|
+
return {
|
|
234
|
+
sessionId: record.sessionId,
|
|
235
|
+
ownerId: record.ownerId,
|
|
236
|
+
command: record.command,
|
|
237
|
+
cwd: record.cwd,
|
|
238
|
+
status: record.status,
|
|
239
|
+
startedAt: record.startedAt,
|
|
240
|
+
updatedAt: record.updatedAt,
|
|
241
|
+
finishedAt: record.finishedAt,
|
|
242
|
+
exitCode: record.exitCode,
|
|
243
|
+
signal: record.signal,
|
|
244
|
+
droppedChars: record.droppedChars
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
normalizeOwnerId(ownerId) {
|
|
248
|
+
const trimmed = ownerId.trim();
|
|
249
|
+
if (!trimmed) throw new Error("Terminal ownerId is required");
|
|
250
|
+
return trimmed;
|
|
251
|
+
}
|
|
252
|
+
safeKill(process, signal) {
|
|
253
|
+
try {
|
|
254
|
+
process.kill(signal);
|
|
255
|
+
} catch {}
|
|
256
|
+
}
|
|
257
|
+
constructor(options = {}){
|
|
258
|
+
_define_property(this, "sessions", new Map());
|
|
259
|
+
_define_property(this, "ownerIndex", new Map());
|
|
260
|
+
_define_property(this, "cleanupTimer", void 0);
|
|
261
|
+
_define_property(this, "maxSessionsPerOwner", void 0);
|
|
262
|
+
_define_property(this, "maxBufferedCharsPerSession", void 0);
|
|
263
|
+
_define_property(this, "maxRuntimeMs", void 0);
|
|
264
|
+
_define_property(this, "idleTimeoutMs", void 0);
|
|
265
|
+
_define_property(this, "completedSessionRetentionMs", void 0);
|
|
266
|
+
_define_property(this, "terminationGraceMs", void 0);
|
|
267
|
+
this.maxSessionsPerOwner = options.maxSessionsPerOwner ?? DEFAULT_MAX_SESSIONS_PER_OWNER;
|
|
268
|
+
this.maxBufferedCharsPerSession = options.maxBufferedCharsPerSession ?? DEFAULT_MAX_BUFFERED_CHARS;
|
|
269
|
+
this.maxRuntimeMs = options.maxRuntimeMs ?? DEFAULT_MAX_RUNTIME_MS;
|
|
270
|
+
this.idleTimeoutMs = options.idleTimeoutMs ?? DEFAULT_IDLE_TIMEOUT_MS;
|
|
271
|
+
this.completedSessionRetentionMs = options.completedSessionRetentionMs ?? DEFAULT_COMPLETED_RETENTION_MS;
|
|
272
|
+
this.terminationGraceMs = options.terminationGraceMs ?? DEFAULT_TERMINATION_GRACE_MS;
|
|
273
|
+
this.cleanupTimer = setInterval(()=>{
|
|
274
|
+
this.cleanupExpiredSessions();
|
|
275
|
+
}, 60000);
|
|
276
|
+
this.cleanupTimer.unref?.();
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
let sharedTerminalSessionManager = null;
|
|
280
|
+
const getSharedTerminalSessionManager = ()=>{
|
|
281
|
+
if (!sharedTerminalSessionManager) sharedTerminalSessionManager = new TerminalSessionManager();
|
|
282
|
+
return sharedTerminalSessionManager;
|
|
283
|
+
};
|
|
284
|
+
export { TerminalSessionManager, getSharedTerminalSessionManager };
|