@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.
Files changed (160) hide show
  1. package/.wingman/agents/README.md +7 -1
  2. package/.wingman/agents/coding/agent.md +299 -201
  3. package/.wingman/agents/coding-v2/agent.md +127 -0
  4. package/.wingman/agents/coding-v2/implementor.md +89 -0
  5. package/.wingman/agents/main/agent.md +4 -0
  6. package/README.md +1 -0
  7. package/dist/agent/config/agentConfig.cjs +31 -17
  8. package/dist/agent/config/agentConfig.d.ts +23 -1
  9. package/dist/agent/config/agentConfig.js +30 -19
  10. package/dist/agent/config/agentLoader.cjs +26 -8
  11. package/dist/agent/config/agentLoader.d.ts +4 -2
  12. package/dist/agent/config/agentLoader.js +26 -8
  13. package/dist/agent/config/modelFactory.cjs +95 -25
  14. package/dist/agent/config/modelFactory.d.ts +13 -1
  15. package/dist/agent/config/modelFactory.js +95 -25
  16. package/dist/agent/config/toolRegistry.cjs +19 -6
  17. package/dist/agent/config/toolRegistry.d.ts +5 -2
  18. package/dist/agent/config/toolRegistry.js +19 -6
  19. package/dist/agent/middleware/hooks/types.cjs +13 -13
  20. package/dist/agent/middleware/hooks/types.d.ts +1 -1
  21. package/dist/agent/middleware/hooks/types.js +14 -14
  22. package/dist/agent/tests/agentConfig.test.cjs +22 -2
  23. package/dist/agent/tests/agentConfig.test.js +22 -2
  24. package/dist/agent/tests/agentLoader.test.cjs +38 -1
  25. package/dist/agent/tests/agentLoader.test.js +38 -1
  26. package/dist/agent/tests/backgroundTerminal.test.cjs +70 -0
  27. package/dist/agent/tests/backgroundTerminal.test.d.ts +1 -0
  28. package/dist/agent/tests/backgroundTerminal.test.js +64 -0
  29. package/dist/agent/tests/commandExecuteTool.test.cjs +29 -0
  30. package/dist/agent/tests/commandExecuteTool.test.d.ts +1 -0
  31. package/dist/agent/tests/commandExecuteTool.test.js +23 -0
  32. package/dist/agent/tests/modelFactory.test.cjs +47 -5
  33. package/dist/agent/tests/modelFactory.test.js +47 -5
  34. package/dist/agent/tests/terminalSessionManager.test.cjs +121 -0
  35. package/dist/agent/tests/terminalSessionManager.test.d.ts +1 -0
  36. package/dist/agent/tests/terminalSessionManager.test.js +115 -0
  37. package/dist/agent/tests/toolRegistry.test.cjs +14 -2
  38. package/dist/agent/tests/toolRegistry.test.js +14 -2
  39. package/dist/agent/tools/background_terminal.cjs +128 -0
  40. package/dist/agent/tools/background_terminal.d.ts +41 -0
  41. package/dist/agent/tools/background_terminal.js +94 -0
  42. package/dist/agent/tools/code_search.cjs +6 -6
  43. package/dist/agent/tools/code_search.d.ts +1 -1
  44. package/dist/agent/tools/code_search.js +7 -7
  45. package/dist/agent/tools/command_execute.cjs +22 -7
  46. package/dist/agent/tools/command_execute.d.ts +3 -2
  47. package/dist/agent/tools/command_execute.js +23 -8
  48. package/dist/agent/tools/git_status.cjs +3 -3
  49. package/dist/agent/tools/git_status.d.ts +1 -1
  50. package/dist/agent/tools/git_status.js +4 -4
  51. package/dist/agent/tools/internet_search.cjs +6 -6
  52. package/dist/agent/tools/internet_search.d.ts +1 -1
  53. package/dist/agent/tools/internet_search.js +7 -7
  54. package/dist/agent/tools/terminal_session_manager.cjs +321 -0
  55. package/dist/agent/tools/terminal_session_manager.d.ts +77 -0
  56. package/dist/agent/tools/terminal_session_manager.js +284 -0
  57. package/dist/agent/tools/think.cjs +4 -4
  58. package/dist/agent/tools/think.d.ts +1 -1
  59. package/dist/agent/tools/think.js +5 -5
  60. package/dist/agent/tools/ui_registry.cjs +13 -13
  61. package/dist/agent/tools/ui_registry.d.ts +4 -4
  62. package/dist/agent/tools/ui_registry.js +14 -14
  63. package/dist/agent/tools/web_crawler.cjs +4 -4
  64. package/dist/agent/tools/web_crawler.d.ts +1 -1
  65. package/dist/agent/tools/web_crawler.js +5 -5
  66. package/dist/agent/utils.cjs +2 -1
  67. package/dist/agent/utils.js +2 -1
  68. package/dist/cli/commands/init.cjs +7 -6
  69. package/dist/cli/commands/init.js +7 -6
  70. package/dist/cli/commands/provider.cjs +17 -3
  71. package/dist/cli/commands/provider.js +17 -3
  72. package/dist/cli/config/loader.cjs +27 -0
  73. package/dist/cli/config/loader.js +27 -0
  74. package/dist/cli/config/schema.cjs +146 -68
  75. package/dist/cli/config/schema.d.ts +89 -1
  76. package/dist/cli/config/schema.js +134 -68
  77. package/dist/cli/core/agentInvoker.cjs +344 -17
  78. package/dist/cli/core/agentInvoker.d.ts +63 -3
  79. package/dist/cli/core/agentInvoker.js +303 -12
  80. package/dist/cli/core/sessionManager.cjs +32 -5
  81. package/dist/cli/core/sessionManager.js +32 -5
  82. package/dist/cli/core/streamParser.cjs +15 -0
  83. package/dist/cli/core/streamParser.js +15 -0
  84. package/dist/cli/index.cjs +6 -5
  85. package/dist/cli/index.js +6 -5
  86. package/dist/cli/types.d.ts +32 -0
  87. package/dist/cli/ui/toolDisplayHelpers.cjs +2 -0
  88. package/dist/cli/ui/toolDisplayHelpers.js +2 -0
  89. package/dist/gateway/hooks/registry.cjs +2 -1
  90. package/dist/gateway/hooks/registry.d.ts +1 -1
  91. package/dist/gateway/hooks/registry.js +2 -1
  92. package/dist/gateway/hooks/types.cjs +11 -11
  93. package/dist/gateway/hooks/types.d.ts +1 -1
  94. package/dist/gateway/hooks/types.js +12 -12
  95. package/dist/gateway/http/agents.cjs +67 -4
  96. package/dist/gateway/http/agents.js +67 -4
  97. package/dist/gateway/http/sessions.cjs +7 -7
  98. package/dist/gateway/http/sessions.js +7 -7
  99. package/dist/gateway/http/types.d.ts +5 -3
  100. package/dist/gateway/http/webhooks.cjs +6 -5
  101. package/dist/gateway/http/webhooks.js +6 -5
  102. package/dist/gateway/server.cjs +198 -41
  103. package/dist/gateway/server.d.ts +9 -1
  104. package/dist/gateway/server.js +198 -41
  105. package/dist/gateway/types.d.ts +1 -0
  106. package/dist/gateway/validation.cjs +39 -39
  107. package/dist/gateway/validation.d.ts +1 -1
  108. package/dist/gateway/validation.js +40 -40
  109. package/dist/providers/codex.cjs +167 -0
  110. package/dist/providers/codex.d.ts +15 -0
  111. package/dist/providers/codex.js +127 -0
  112. package/dist/providers/credentials.cjs +8 -0
  113. package/dist/providers/credentials.js +8 -0
  114. package/dist/providers/registry.cjs +11 -0
  115. package/dist/providers/registry.d.ts +1 -1
  116. package/dist/providers/registry.js +11 -0
  117. package/dist/tests/additionalMessageMiddleware.test.cjs +3 -0
  118. package/dist/tests/additionalMessageMiddleware.test.js +3 -0
  119. package/dist/tests/agentInvokerSummarization.test.cjs +455 -0
  120. package/dist/tests/agentInvokerSummarization.test.d.ts +1 -0
  121. package/dist/tests/agentInvokerSummarization.test.js +449 -0
  122. package/dist/tests/agents-api.test.cjs +45 -5
  123. package/dist/tests/agents-api.test.js +45 -5
  124. package/dist/tests/cli-config-loader.test.cjs +88 -0
  125. package/dist/tests/cli-config-loader.test.js +88 -0
  126. package/dist/tests/cli-init.test.cjs +27 -3
  127. package/dist/tests/cli-init.test.js +27 -3
  128. package/dist/tests/codex-credentials-precedence.test.cjs +94 -0
  129. package/dist/tests/codex-credentials-precedence.test.d.ts +1 -0
  130. package/dist/tests/codex-credentials-precedence.test.js +88 -0
  131. package/dist/tests/codex-provider.test.cjs +210 -0
  132. package/dist/tests/codex-provider.test.d.ts +1 -0
  133. package/dist/tests/codex-provider.test.js +204 -0
  134. package/dist/tests/gateway.test.cjs +115 -8
  135. package/dist/tests/gateway.test.js +115 -8
  136. package/dist/tests/provider-command-codex.test.cjs +57 -0
  137. package/dist/tests/provider-command-codex.test.d.ts +1 -0
  138. package/dist/tests/provider-command-codex.test.js +51 -0
  139. package/dist/tests/sessionStateMessages.test.cjs +38 -0
  140. package/dist/tests/sessionStateMessages.test.js +38 -0
  141. package/dist/tests/toolDisplayHelpers.test.cjs +3 -0
  142. package/dist/tests/toolDisplayHelpers.test.js +3 -0
  143. package/dist/tools/mcp-finance.cjs +48 -48
  144. package/dist/tools/mcp-finance.js +48 -48
  145. package/dist/types/mcp.cjs +15 -15
  146. package/dist/types/mcp.d.ts +1 -1
  147. package/dist/types/mcp.js +16 -16
  148. package/dist/types/voice.cjs +21 -21
  149. package/dist/types/voice.d.ts +1 -1
  150. package/dist/types/voice.js +22 -22
  151. package/dist/webui/assets/index-DVWQluit.css +11 -0
  152. package/dist/webui/assets/index-Dlyzwalc.js +270 -0
  153. package/dist/webui/favicon-32x32.png +0 -0
  154. package/dist/webui/favicon-64x64.png +0 -0
  155. package/dist/webui/favicon.webp +0 -0
  156. package/dist/webui/index.html +4 -2
  157. package/package.json +13 -12
  158. package/.wingman/agents/coding/implementor.md +0 -79
  159. package/dist/webui/assets/index-CPhfGPHc.js +0 -182
  160. 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 };