@a1hvdy/cc-openclaw 0.5.2 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/dist/src/command-router/cc-handler.js +72 -0
  2. package/dist/src/command-router/cc-handler.js.map +1 -1
  3. package/dist/src/constants.d.ts +9 -0
  4. package/dist/src/constants.js +10 -0
  5. package/dist/src/constants.js.map +1 -1
  6. package/dist/src/engines/persistent-session.d.ts +2 -0
  7. package/dist/src/engines/persistent-session.js +41 -11
  8. package/dist/src/engines/persistent-session.js.map +1 -1
  9. package/dist/src/lib/config.d.ts +2 -0
  10. package/dist/src/lib/config.js +19 -0
  11. package/dist/src/lib/config.js.map +1 -1
  12. package/dist/src/lib/sysprompt-strip.js +12 -12
  13. package/dist/src/lib/sysprompt-strip.js.map +1 -1
  14. package/dist/src/lib/trajectory.d.ts +1 -1
  15. package/dist/src/lib/trajectory.js.map +1 -1
  16. package/dist/src/lib/vendor-paths.d.ts +6 -4
  17. package/dist/src/lib/vendor-paths.js +21 -14
  18. package/dist/src/lib/vendor-paths.js.map +1 -1
  19. package/dist/src/openai-compat/openai-compat.d.ts +7 -1
  20. package/dist/src/openai-compat/openai-compat.js +8 -1
  21. package/dist/src/openai-compat/openai-compat.js.map +1 -1
  22. package/dist/src/openai-compat/sse-translator.d.ts +23 -3
  23. package/dist/src/openai-compat/sse-translator.js +45 -6
  24. package/dist/src/openai-compat/sse-translator.js.map +1 -1
  25. package/dist/src/session-bootstrap/cwd-patch.js +59 -28
  26. package/dist/src/session-bootstrap/cwd-patch.js.map +1 -1
  27. package/dist/src/types.d.ts +1 -0
  28. package/package.json +2 -3
  29. package/vendor/base-oneshot-session.d.ts +0 -87
  30. package/vendor/base-oneshot-session.js +0 -227
  31. package/vendor/base-oneshot-session.js.map +0 -1
  32. package/vendor/circuit-breaker.d.ts +0 -21
  33. package/vendor/circuit-breaker.js +0 -47
  34. package/vendor/circuit-breaker.js.map +0 -1
  35. package/vendor/consensus.d.ts +0 -20
  36. package/vendor/consensus.js +0 -52
  37. package/vendor/consensus.js.map +0 -1
  38. package/vendor/constants.d.ts +0 -130
  39. package/vendor/constants.js +0 -139
  40. package/vendor/constants.js.map +0 -1
  41. package/vendor/council.d.ts +0 -67
  42. package/vendor/council.js +0 -913
  43. package/vendor/council.js.map +0 -1
  44. package/vendor/embedded-server.d.ts +0 -25
  45. package/vendor/embedded-server.js +0 -373
  46. package/vendor/embedded-server.js.map +0 -1
  47. package/vendor/inbox-manager.d.ts +0 -38
  48. package/vendor/inbox-manager.js +0 -111
  49. package/vendor/inbox-manager.js.map +0 -1
  50. package/vendor/index.d.ts +0 -63
  51. package/vendor/index.js +0 -705
  52. package/vendor/index.js.map +0 -1
  53. package/vendor/logger.d.ts +0 -16
  54. package/vendor/logger.js +0 -44
  55. package/vendor/logger.js.map +0 -1
  56. package/vendor/models.d.ts +0 -69
  57. package/vendor/models.js +0 -289
  58. package/vendor/models.js.map +0 -1
  59. package/vendor/openai-compat.d.ts +0 -197
  60. package/vendor/openai-compat.js +0 -765
  61. package/vendor/openai-compat.js.map +0 -1
  62. package/vendor/persistent-codex-session.d.ts +0 -16
  63. package/vendor/persistent-codex-session.js +0 -105
  64. package/vendor/persistent-codex-session.js.map +0 -1
  65. package/vendor/persistent-cursor-session.d.ts +0 -21
  66. package/vendor/persistent-cursor-session.js +0 -241
  67. package/vendor/persistent-cursor-session.js.map +0 -1
  68. package/vendor/persistent-custom-session.d.ts +0 -78
  69. package/vendor/persistent-custom-session.js +0 -937
  70. package/vendor/persistent-custom-session.js.map +0 -1
  71. package/vendor/persistent-gemini-session.d.ts +0 -21
  72. package/vendor/persistent-gemini-session.js +0 -216
  73. package/vendor/persistent-gemini-session.js.map +0 -1
  74. package/vendor/persistent-session.d.ts +0 -74
  75. package/vendor/persistent-session.js +0 -684
  76. package/vendor/persistent-session.js.map +0 -1
  77. package/vendor/proxy/anthropic-adapter.d.ts +0 -136
  78. package/vendor/proxy/anthropic-adapter.js +0 -392
  79. package/vendor/proxy/anthropic-adapter.js.map +0 -1
  80. package/vendor/proxy/handler.d.ts +0 -39
  81. package/vendor/proxy/handler.js +0 -323
  82. package/vendor/proxy/handler.js.map +0 -1
  83. package/vendor/proxy/schema-cleaner.d.ts +0 -11
  84. package/vendor/proxy/schema-cleaner.js +0 -34
  85. package/vendor/proxy/schema-cleaner.js.map +0 -1
  86. package/vendor/proxy/thought-cache.d.ts +0 -19
  87. package/vendor/proxy/thought-cache.js +0 -53
  88. package/vendor/proxy/thought-cache.js.map +0 -1
  89. package/vendor/session-manager.d.ts +0 -211
  90. package/vendor/session-manager.js +0 -1345
  91. package/vendor/session-manager.js.map +0 -1
  92. package/vendor/skill-resolver.js +0 -107
  93. package/vendor/types.d.ts +0 -466
  94. package/vendor/types.js +0 -8
  95. package/vendor/types.js.map +0 -1
  96. package/vendor/validation.d.ts +0 -31
  97. package/vendor/validation.js +0 -104
  98. package/vendor/validation.js.map +0 -1
@@ -1,937 +0,0 @@
1
- /**
2
- * Persistent Custom Session — generic wrapper for any coding agent CLI
3
- *
4
- * Supports two operating modes based on CustomEngineConfig.persistent:
5
- *
6
- * persistent=true — long-running subprocess with stream-json I/O over
7
- * stdin/stdout (like Claude Code). Started once, messages
8
- * sent as JSON lines on stdin.
9
- *
10
- * persistent=false — one-shot per send (like Gemini/Codex). Each send()
11
- * spawns a new process with the message as a CLI argument.
12
- *
13
- * The config maps OpenClaw session concepts (permission modes, models, etc.)
14
- * to the target CLI's flags, so any coding agent with a CLI can be integrated
15
- * without writing engine-specific code.
16
- */
17
- import { spawn } from 'node:child_process';
18
- import { EventEmitter } from 'node:events';
19
- import * as readline from 'node:readline';
20
- import * as fs from 'node:fs';
21
- import * as path from 'node:path';
22
- import { getModelPricing as _getModelPricingBase, } from './types.js';
23
- import { resolveAlias, estimateTokens } from './models.js';
24
- import { CONTEXT_HIGH_THRESHOLD, MAX_HISTORY_ITEMS, DEFAULT_HISTORY_LIMIT, SESSION_READY_TIMEOUT_MS, SESSION_READY_FALLBACK_MS, TURN_TIMEOUT_MS, COMPACT_TIMEOUT_MS, STOP_SIGKILL_DELAY_MS, SESSION_EVENT, } from './constants.js';
25
- // ─── Helpers ────────────────────────────────────────────────────────────────
26
- function getModelPricing(model, engineConfig) {
27
- // Try the model registry first; fall back to engine-level pricing
28
- const base = _getModelPricingBase(model, 'claude-sonnet-4-6');
29
- if (base.input === 0 && base.output === 0 && engineConfig.pricing) {
30
- return engineConfig.pricing;
31
- }
32
- return base;
33
- }
34
- function resolveBin(engineConfig) {
35
- if (engineConfig.binEnv) {
36
- const envVal = process.env[engineConfig.binEnv];
37
- if (envVal)
38
- return envVal;
39
- }
40
- return engineConfig.bin;
41
- }
42
- /** Build sanitizer function from config patterns + common defaults */
43
- function buildSanitizer(engineConfig) {
44
- const patterns = [
45
- // Always sanitize Bearer tokens and common API key patterns
46
- { re: /Bearer [a-zA-Z0-9_-]+/g, replacement: 'Bearer ***' },
47
- { re: /sk-[a-zA-Z0-9_-]{10,}/g, replacement: 'sk-***' },
48
- ];
49
- if (engineConfig.sanitizePatterns) {
50
- for (const p of engineConfig.sanitizePatterns) {
51
- try {
52
- patterns.push({ re: new RegExp(p, 'g'), replacement: '***' });
53
- }
54
- catch {
55
- // Invalid regex — skip silently
56
- }
57
- }
58
- }
59
- return (text) => {
60
- let result = text;
61
- for (const { re, replacement } of patterns) {
62
- re.lastIndex = 0;
63
- result = result.replace(re, replacement);
64
- }
65
- return result;
66
- };
67
- }
68
- // ─── PersistentCustomSession ───────────────────────────────────────────────
69
- export class PersistentCustomSession extends EventEmitter {
70
- options;
71
- engineConfig;
72
- engineBin;
73
- sanitize;
74
- // Persistent mode state
75
- proc = null;
76
- _rl = null;
77
- _streamCallbacks = null;
78
- _contextHighFired = false;
79
- // One-shot mode state
80
- currentProc = null;
81
- _currentRl = null;
82
- // Shared state
83
- _isReady = false;
84
- _isPaused = false;
85
- _isBusy = false;
86
- currentRequestId = 0;
87
- _startTime = null;
88
- _history = [];
89
- sessionId;
90
- _stats = {
91
- turns: 0,
92
- toolCalls: 0,
93
- toolErrors: 0,
94
- tokensIn: 0,
95
- tokensOut: 0,
96
- cachedTokens: 0,
97
- costUsd: 0,
98
- lastActivity: null,
99
- };
100
- constructor(config) {
101
- super();
102
- if (!config.customEngine) {
103
- throw new Error('CustomEngineConfig is required for custom engine sessions');
104
- }
105
- this.engineConfig = config.customEngine;
106
- this.engineBin = resolveBin(this.engineConfig);
107
- this.sanitize = buildSanitizer(this.engineConfig);
108
- this.options = {
109
- ...config,
110
- permissionMode: config.permissionMode || 'acceptEdits',
111
- };
112
- }
113
- get pid() {
114
- return this.proc?.pid ?? this.currentProc?.pid ?? undefined;
115
- }
116
- get isReady() {
117
- return this._isReady;
118
- }
119
- get isPaused() {
120
- return this._isPaused;
121
- }
122
- get isBusy() {
123
- return this._isBusy;
124
- }
125
- // ─── Start ───────────────────────────────────────────────────────────────
126
- async start() {
127
- // Normalize CWD
128
- if (this.options.cwd) {
129
- this.options.cwd = path.resolve(this.options.cwd);
130
- if (!fs.existsSync(this.options.cwd)) {
131
- fs.mkdirSync(this.options.cwd, { recursive: true });
132
- }
133
- }
134
- if (this.engineConfig.persistent) {
135
- return this._startPersistent();
136
- }
137
- else {
138
- return this._startOneShot();
139
- }
140
- }
141
- // ── Persistent mode start ───────────────────────────────────────────────
142
- async _startPersistent() {
143
- const a = this.engineConfig.args;
144
- const args = [];
145
- // Print mode
146
- if (a.print)
147
- args.push(a.print);
148
- // I/O format
149
- if (a.inputFormat && a.inputFormatValue)
150
- args.push(a.inputFormat, a.inputFormatValue);
151
- if (a.outputFormat && a.outputFormatValue)
152
- args.push(a.outputFormat, a.outputFormatValue);
153
- // Streaming flags
154
- if (a.replayUserMessages)
155
- args.push(a.replayUserMessages);
156
- if (a.verbose)
157
- args.push(a.verbose);
158
- if (a.includePartialMessages)
159
- args.push(a.includePartialMessages);
160
- // Permission mode
161
- this._appendPermissionArgs(args);
162
- // Model
163
- if (this.options.model && a.model) {
164
- args.push(a.model, this.options.model);
165
- }
166
- // Resume
167
- if (this.options.resumeSessionId && a.resume) {
168
- args.push(a.resume, this.options.resumeSessionId);
169
- }
170
- // System prompts
171
- if (this.options.systemPrompt && a.systemPrompt) {
172
- args.push(a.systemPrompt, this.options.systemPrompt);
173
- }
174
- if (this.options.appendSystemPrompt && a.appendSystemPrompt) {
175
- args.push(a.appendSystemPrompt, this.options.appendSystemPrompt);
176
- }
177
- // Limits
178
- if (this.options.maxTurns && a.maxTurns) {
179
- args.push(a.maxTurns, String(this.options.maxTurns));
180
- }
181
- // Skip permissions
182
- if (this.options.dangerouslySkipPermissions && a.skipPermissions) {
183
- args.push(a.skipPermissions);
184
- }
185
- // Effort
186
- if (this.options.effort && this.options.effort !== 'auto' && a.effort) {
187
- args.push(a.effort, this.options.effort);
188
- }
189
- // Extra static args
190
- if (a.extra?.length)
191
- args.push(...a.extra);
192
- // Spawn environment
193
- const spawnEnv = {
194
- ...process.env,
195
- PATH: process.env.PATH || '/usr/local/bin:/usr/bin:/bin',
196
- ...this.engineConfig.env,
197
- };
198
- this.proc = spawn(this.engineBin, args, {
199
- cwd: this.options.cwd,
200
- env: spawnEnv,
201
- stdio: ['pipe', 'pipe', 'pipe'],
202
- detached: true,
203
- });
204
- this.proc.unref();
205
- // Parse stdout line-by-line
206
- this._rl = readline.createInterface({ input: this.proc.stdout, crlfDelay: Infinity });
207
- this._rl.on('line', (line) => {
208
- if (!line.trim())
209
- return;
210
- try {
211
- const event = JSON.parse(line);
212
- this._handlePersistentEvent(event);
213
- }
214
- catch {
215
- this.emit(SESSION_EVENT.LOG, `[${this.engineConfig.name}-stdout] ${line}`);
216
- }
217
- });
218
- this.proc.stderr?.on('data', (data) => {
219
- this.emit(SESSION_EVENT.LOG, `[${this.engineConfig.name}-stderr] ${this.sanitize(data.toString())}`);
220
- });
221
- this.proc.on('close', (code) => {
222
- this._isReady = false;
223
- this.emit(SESSION_EVENT.CLOSE, code);
224
- });
225
- this.proc.on('error', (err) => {
226
- this.emit(SESSION_EVENT.ERROR, err);
227
- });
228
- // Wait for ready
229
- return new Promise((resolve, reject) => {
230
- const timeout = setTimeout(() => reject(new Error(`Timeout waiting for ${this.engineConfig.name} session ready`)), SESSION_READY_TIMEOUT_MS);
231
- this.once(SESSION_EVENT.READY, () => {
232
- clearTimeout(timeout);
233
- resolve(this);
234
- });
235
- this.once(SESSION_EVENT.ERROR, (err) => {
236
- clearTimeout(timeout);
237
- reject(err);
238
- });
239
- const onCloseBeforeReady = (code) => {
240
- if (!this._isReady) {
241
- clearTimeout(timeout);
242
- reject(new Error(`${this.engineConfig.name} process exited prematurely with code ${code}. Session failed to start.`));
243
- }
244
- };
245
- this.once(SESSION_EVENT.CLOSE, onCloseBeforeReady);
246
- const onInit = () => {
247
- if (!this._isReady) {
248
- this._isReady = true;
249
- this.removeListener(SESSION_EVENT.CLOSE, onCloseBeforeReady);
250
- this.emit(SESSION_EVENT.READY);
251
- }
252
- };
253
- this.once(SESSION_EVENT.INIT, onInit);
254
- // Fallback: mark ready after 2s if no init event
255
- setTimeout(() => {
256
- this.removeListener(SESSION_EVENT.INIT, onInit);
257
- if (this.proc?.killed || this.proc?.exitCode !== null) {
258
- clearTimeout(timeout);
259
- this.removeListener(SESSION_EVENT.CLOSE, onCloseBeforeReady);
260
- reject(new Error(`${this.engineConfig.name} CLI crashed on startup. Fallback timer aborted.`));
261
- return;
262
- }
263
- if (!this._isReady) {
264
- this._isReady = true;
265
- this.removeListener(SESSION_EVENT.CLOSE, onCloseBeforeReady);
266
- this.emit(SESSION_EVENT.READY);
267
- }
268
- }, SESSION_READY_FALLBACK_MS);
269
- });
270
- }
271
- // ── One-shot mode start ─────────────────────────────────────────────────
272
- async _startOneShot() {
273
- const eName = this.engineConfig.name;
274
- this.sessionId = `${eName}-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
275
- this._startTime = new Date().toISOString();
276
- this._isReady = true;
277
- this.emit(SESSION_EVENT.READY);
278
- this.emit(SESSION_EVENT.INIT, { type: 'system', subtype: 'init', session_id: this.sessionId });
279
- return this;
280
- }
281
- // ─── Send ────────────────────────────────────────────────────────────────
282
- async send(message, options = {}) {
283
- if (!this._isReady)
284
- throw new Error('Session not ready. Call start() first.');
285
- if (this.engineConfig.persistent) {
286
- return this._sendPersistent(message, options);
287
- }
288
- else {
289
- return this._sendOneShot(message, options);
290
- }
291
- }
292
- // ── Persistent mode send ────────────────────────────────────────────────
293
- async _sendPersistent(message, options) {
294
- if (!this.proc)
295
- throw new Error('Session not ready. Call start() first.');
296
- const requestId = ++this.currentRequestId;
297
- let finalMessage = typeof message === 'string' ? message : message;
298
- if (typeof finalMessage === 'string') {
299
- if (options.effort === 'high' || options.effort === 'max') {
300
- finalMessage = `ultrathink\n\n${finalMessage}`;
301
- }
302
- if (options.plan) {
303
- finalMessage = `/plan ${finalMessage}`;
304
- }
305
- }
306
- const payload = {
307
- type: 'user',
308
- message: {
309
- role: 'user',
310
- content: typeof finalMessage === 'string' ? [{ type: 'text', text: finalMessage }] : finalMessage,
311
- },
312
- };
313
- this.proc.stdin.write(JSON.stringify(payload) + '\n');
314
- if (options.callbacks)
315
- this._streamCallbacks = options.callbacks;
316
- if (options.waitForComplete) {
317
- this._isBusy = true;
318
- try {
319
- return await this._waitForTurnComplete(options.timeout || TURN_TIMEOUT_MS);
320
- }
321
- finally {
322
- this._isBusy = false;
323
- if (options.callbacks)
324
- this._streamCallbacks = null;
325
- }
326
- }
327
- return { requestId, sent: true };
328
- }
329
- // ── One-shot mode send ──────────────────────────────────────────────────
330
- async _sendOneShot(message, options) {
331
- const requestId = ++this.currentRequestId;
332
- const textMessage = typeof message === 'string' ? message : JSON.stringify(message);
333
- if (!options.waitForComplete) {
334
- this._runOneShot(textMessage, options).catch((err) => this.emit(SESSION_EVENT.ERROR, err));
335
- return { requestId, sent: true };
336
- }
337
- this._isBusy = true;
338
- try {
339
- return await this._runOneShot(textMessage, options);
340
- }
341
- finally {
342
- this._isBusy = false;
343
- }
344
- }
345
- async _runOneShot(message, options) {
346
- const a = this.engineConfig.args;
347
- const args = [];
348
- // Print mode + message
349
- if (a.print)
350
- args.push(a.print);
351
- args.push(message);
352
- // Output format
353
- if (a.outputFormat && a.outputFormatValue)
354
- args.push(a.outputFormat, a.outputFormatValue);
355
- // Permission mode
356
- this._appendPermissionArgs(args);
357
- // Skip permissions
358
- if (this.options.dangerouslySkipPermissions && a.skipPermissions) {
359
- args.push(a.skipPermissions);
360
- }
361
- // Model
362
- if (this.options.model && a.model)
363
- args.push(a.model, this.options.model);
364
- // System prompts
365
- if (this.options.systemPrompt && a.systemPrompt) {
366
- args.push(a.systemPrompt, this.options.systemPrompt);
367
- }
368
- // Max turns
369
- if (this.options.maxTurns && a.maxTurns) {
370
- args.push(a.maxTurns, String(this.options.maxTurns));
371
- }
372
- // Effort
373
- if (this.options.effort && this.options.effort !== 'auto' && a.effort) {
374
- args.push(a.effort, this.options.effort);
375
- }
376
- // Workspace
377
- if (this.options.cwd && a.workspace) {
378
- args.push(a.workspace, this.options.cwd);
379
- }
380
- // Extra static args
381
- if (a.extra?.length)
382
- args.push(...a.extra);
383
- const timeout = options.timeout || 300_000;
384
- const spawnEnv = {
385
- ...process.env,
386
- ...this.engineConfig.env,
387
- };
388
- return new Promise((resolve, reject) => {
389
- const resultText = { value: '' };
390
- let stderr = '';
391
- let settled = false;
392
- let gotUsageFromEvents = false;
393
- const proc = spawn(this.engineBin, args, {
394
- cwd: this.options.cwd,
395
- env: spawnEnv,
396
- stdio: ['pipe', 'pipe', 'pipe'],
397
- });
398
- this.currentProc = proc;
399
- const timer = setTimeout(() => {
400
- if (!settled) {
401
- settled = true;
402
- proc.kill('SIGTERM');
403
- reject(new Error(`Timeout waiting for ${this.engineConfig.name} response`));
404
- }
405
- }, timeout);
406
- const rl = readline.createInterface({ input: proc.stdout, crlfDelay: Infinity });
407
- this._currentRl = rl;
408
- rl.on('line', (line) => {
409
- if (!line.trim())
410
- return;
411
- try {
412
- const event = JSON.parse(line);
413
- this._handleOneShotEvent(event, options, resultText, () => {
414
- gotUsageFromEvents = true;
415
- });
416
- }
417
- catch {
418
- // Non-JSON line — treat as plain text
419
- resultText.value += line + '\n';
420
- try {
421
- options.callbacks?.onText?.(line + '\n');
422
- }
423
- catch {
424
- /* ignore callback errors */
425
- }
426
- this.emit(SESSION_EVENT.TEXT, line + '\n');
427
- }
428
- });
429
- proc.stderr?.on('data', (data) => {
430
- const sanitized = this.sanitize(data.toString());
431
- stderr += sanitized;
432
- this.emit(SESSION_EVENT.LOG, `[${this.engineConfig.name}-stderr] ${sanitized}`);
433
- });
434
- proc.on('close', (code) => {
435
- clearTimeout(timer);
436
- this.currentProc = null;
437
- if (this._currentRl) {
438
- this._currentRl.close();
439
- this._currentRl = null;
440
- }
441
- if (settled)
442
- return;
443
- settled = true;
444
- const now = new Date().toISOString();
445
- this._stats.turns++;
446
- this._stats.lastActivity = now;
447
- if (!gotUsageFromEvents && resultText.value.length > 0) {
448
- this._stats.tokensIn += estimateTokens(message);
449
- this._stats.tokensOut += estimateTokens(resultText.value);
450
- this._updateCost();
451
- }
452
- this._history.push({ time: now, type: 'result', event: { text: resultText.value, code } });
453
- if (this._history.length > MAX_HISTORY_ITEMS)
454
- this._history.shift();
455
- const event = {
456
- type: 'result',
457
- result: resultText.value,
458
- stop_reason: code === 0 ? 'end_turn' : 'error',
459
- };
460
- this.emit(SESSION_EVENT.RESULT, event);
461
- this.emit(SESSION_EVENT.TURN_COMPLETE, event);
462
- if (code !== 0 && !resultText.value) {
463
- reject(new Error(stderr || `${this.engineConfig.name} exited with code ${code}`));
464
- }
465
- else if (code !== 0) {
466
- reject(new Error(stderr || `${this.engineConfig.name} exited with code ${code}`));
467
- }
468
- else {
469
- resolve({ text: resultText.value, event });
470
- }
471
- });
472
- proc.on('error', (err) => {
473
- clearTimeout(timer);
474
- if (!settled) {
475
- settled = true;
476
- reject(err);
477
- }
478
- });
479
- });
480
- }
481
- // ─── Event Handling (Persistent mode) ───────────────────────────────────
482
- _handlePersistentEvent(event) {
483
- const type = event.type;
484
- this._stats.lastActivity = new Date().toISOString();
485
- this._history.push({ time: this._stats.lastActivity, type, event });
486
- if (this._history.length > MAX_HISTORY_ITEMS)
487
- this._history.shift();
488
- switch (type) {
489
- case 'system':
490
- if (event.subtype === 'init') {
491
- this.sessionId = event.session_id;
492
- this._startTime = new Date().toISOString();
493
- this.emit(SESSION_EVENT.INIT, event);
494
- }
495
- this.emit(SESSION_EVENT.SYSTEM, event);
496
- break;
497
- case 'stream_event': {
498
- const inner = event.event;
499
- if (!inner)
500
- break;
501
- const innerType = inner.type;
502
- if (innerType === 'content_block_start') {
503
- const block = inner.content_block;
504
- if (block?.type === 'tool_use') {
505
- this._stats.toolCalls++;
506
- const toolEvent = { tool: { name: block.name, input: {} } };
507
- try {
508
- this._streamCallbacks?.onToolUse?.(toolEvent);
509
- }
510
- catch {
511
- /* ignore */
512
- }
513
- this.emit(SESSION_EVENT.TOOL_USE, toolEvent);
514
- }
515
- }
516
- else if (innerType === 'content_block_delta') {
517
- const delta = inner.delta;
518
- if (delta?.type === 'text_delta' && delta.text) {
519
- try {
520
- this._streamCallbacks?.onText?.(delta.text);
521
- }
522
- catch {
523
- /* ignore */
524
- }
525
- this.emit(SESSION_EVENT.TEXT, delta.text);
526
- }
527
- }
528
- else if (innerType === 'message_delta') {
529
- const usage = inner.usage;
530
- if (usage) {
531
- this._stats.tokensIn += usage.input_tokens || 0;
532
- this._stats.tokensOut += usage.output_tokens || 0;
533
- this._stats.cachedTokens += usage.cache_read_input_tokens || 0;
534
- this._updateCost();
535
- }
536
- }
537
- this.emit(SESSION_EVENT.STREAM_EVENT, event);
538
- break;
539
- }
540
- case 'user':
541
- this._stats.turns++;
542
- this.emit(SESSION_EVENT.USER_ECHO, event);
543
- break;
544
- case 'assistant':
545
- this.emit(SESSION_EVENT.ASSISTANT, event);
546
- if (event.message?.content && Array.isArray(event.message.content)) {
547
- for (const block of event.message.content) {
548
- if (block.type === 'tool_use') {
549
- this._stats.toolCalls++;
550
- const toolEvent = {
551
- tool: {
552
- name: block.name,
553
- input: block.input || {},
554
- },
555
- };
556
- try {
557
- this._streamCallbacks?.onToolUse?.(toolEvent);
558
- }
559
- catch {
560
- /* ignore */
561
- }
562
- this.emit(SESSION_EVENT.TOOL_USE, toolEvent);
563
- }
564
- }
565
- }
566
- break;
567
- case 'tool_use':
568
- this._stats.toolCalls++;
569
- try {
570
- this._streamCallbacks?.onToolUse?.(event);
571
- }
572
- catch {
573
- /* ignore */
574
- }
575
- this.emit(SESSION_EVENT.TOOL_USE, event);
576
- break;
577
- case 'tool_result':
578
- try {
579
- this._streamCallbacks?.onToolResult?.(event);
580
- }
581
- catch {
582
- /* ignore */
583
- }
584
- if (event.is_error || event.error) {
585
- this._stats.toolErrors++;
586
- }
587
- this.emit(SESSION_EVENT.TOOL_RESULT, event);
588
- break;
589
- case 'error':
590
- this.emit(SESSION_EVENT.ERROR, new Error(String(event.error) || JSON.stringify(event)));
591
- break;
592
- case 'result': {
593
- const usage = event.usage;
594
- if (usage) {
595
- this._stats.tokensIn += usage.input_tokens || 0;
596
- this._stats.tokensOut += usage.output_tokens || 0;
597
- this._stats.cachedTokens += usage.cache_read_input_tokens || 0;
598
- this._updateCost();
599
- }
600
- this.emit(SESSION_EVENT.RESULT, event);
601
- this.emit(SESSION_EVENT.TURN_COMPLETE, event);
602
- const totalTokens = this._stats.tokensIn + this._stats.tokensOut;
603
- if (totalTokens > CONTEXT_HIGH_THRESHOLD && !this._contextHighFired) {
604
- this._contextHighFired = true;
605
- }
606
- break;
607
- }
608
- default:
609
- this.emit(SESSION_EVENT.EVENT, event);
610
- }
611
- }
612
- // ─── Event Handling (One-shot mode) ─────────────────────────────────────
613
- _handleOneShotEvent(event, options, resultText, markUsageReceived) {
614
- const type = event.type;
615
- switch (type) {
616
- case 'system':
617
- if (event.session_id)
618
- this.sessionId = String(event.session_id);
619
- break;
620
- case 'user':
621
- // Echo of user prompt — skip
622
- break;
623
- case 'assistant': {
624
- const msg = event.message;
625
- if (!msg)
626
- break;
627
- const contentArr = msg.content;
628
- if (contentArr) {
629
- for (const block of contentArr) {
630
- if (block.type === 'text' && block.text) {
631
- resultText.value += block.text;
632
- try {
633
- options.callbacks?.onText?.(block.text);
634
- }
635
- catch {
636
- /* ignore */
637
- }
638
- this.emit(SESSION_EVENT.TEXT, block.text);
639
- }
640
- }
641
- }
642
- break;
643
- }
644
- case 'message': {
645
- if (event.role === 'user')
646
- break;
647
- const text = event.content || '';
648
- if (text) {
649
- resultText.value += text;
650
- try {
651
- options.callbacks?.onText?.(text);
652
- }
653
- catch {
654
- /* ignore */
655
- }
656
- this.emit(SESSION_EVENT.TEXT, text);
657
- }
658
- break;
659
- }
660
- case 'tool_use':
661
- this._stats.toolCalls++;
662
- try {
663
- options.callbacks?.onToolUse?.(event);
664
- }
665
- catch {
666
- /* ignore */
667
- }
668
- this.emit(SESSION_EVENT.TOOL_USE, event);
669
- break;
670
- case 'tool_result':
671
- try {
672
- options.callbacks?.onToolResult?.(event);
673
- }
674
- catch {
675
- /* ignore */
676
- }
677
- if (event.is_error)
678
- this._stats.toolErrors++;
679
- this.emit(SESSION_EVENT.TOOL_RESULT, event);
680
- break;
681
- case 'result': {
682
- const usage = event.usage;
683
- if (usage) {
684
- this._stats.tokensIn += usage.input_tokens || usage.inputTokens || usage.prompt_tokens || 0;
685
- this._stats.tokensOut += usage.output_tokens || usage.outputTokens || usage.completion_tokens || 0;
686
- const cached = usage.cache_read_input_tokens || usage.cached_tokens || 0;
687
- if (cached)
688
- this._stats.cachedTokens += cached;
689
- this._updateCost();
690
- markUsageReceived();
691
- }
692
- const resultStr = event.result;
693
- if (resultStr && !resultText.value)
694
- resultText.value = resultStr;
695
- break;
696
- }
697
- case 'error':
698
- this.emit(SESSION_EVENT.LOG, `[${this.engineConfig.name}-error] ${event.error || JSON.stringify(event)}`);
699
- break;
700
- default:
701
- break;
702
- }
703
- }
704
- // ─── Wait for Turn Complete (Persistent mode) ───────────────────────────
705
- _waitForTurnComplete(timeout) {
706
- return new Promise((resolve, reject) => {
707
- let settled = false;
708
- let streamedText = '';
709
- let allAssistantText = '';
710
- const toolNames = [];
711
- const onText = (chunk) => {
712
- streamedText += chunk;
713
- };
714
- this.on(SESSION_EVENT.TEXT, onText);
715
- const onAssistant = (event) => {
716
- if (event.message?.content && Array.isArray(event.message.content)) {
717
- for (const block of event.message.content) {
718
- if (block.type === 'text' && block.text)
719
- allAssistantText += block.text + '\n';
720
- }
721
- }
722
- };
723
- this.on(SESSION_EVENT.ASSISTANT, onAssistant);
724
- const onToolUse = (event) => {
725
- const tool = event.tool;
726
- toolNames.push(tool?.name || event.name || 'unknown');
727
- };
728
- this.on(SESSION_EVENT.TOOL_USE, onToolUse);
729
- const cleanup = () => {
730
- clearTimeout(timer);
731
- this.removeListener(SESSION_EVENT.TEXT, onText);
732
- this.removeListener(SESSION_EVENT.ASSISTANT, onAssistant);
733
- this.removeListener(SESSION_EVENT.TOOL_USE, onToolUse);
734
- this.removeListener(SESSION_EVENT.TURN_COMPLETE, onTurnComplete);
735
- this.removeListener(SESSION_EVENT.ERROR, onError);
736
- this.removeListener(SESSION_EVENT.CLOSE, onClose);
737
- };
738
- const timer = setTimeout(() => {
739
- if (settled)
740
- return;
741
- settled = true;
742
- cleanup();
743
- reject(new Error('Timeout waiting for response'));
744
- }, timeout);
745
- const onTurnComplete = (event) => {
746
- if (settled)
747
- return;
748
- settled = true;
749
- cleanup();
750
- let text = event.result || streamedText || allAssistantText.trim() || '';
751
- if (!text && toolNames.length > 0) {
752
- const unique = [...new Set(toolNames)];
753
- text = `[Agent completed ${toolNames.length} tool calls: ${unique.join(', ')}]`;
754
- }
755
- resolve({ text, event });
756
- };
757
- const onError = (err) => {
758
- if (settled)
759
- return;
760
- settled = true;
761
- cleanup();
762
- reject(err);
763
- };
764
- const onClose = (code) => {
765
- if (settled)
766
- return;
767
- settled = true;
768
- cleanup();
769
- const text = streamedText || allAssistantText.trim() || '';
770
- resolve({
771
- text,
772
- event: {
773
- type: 'result',
774
- result: text,
775
- stop_reason: 'process_exit',
776
- exit_code: code,
777
- },
778
- });
779
- };
780
- this.once(SESSION_EVENT.TURN_COMPLETE, onTurnComplete);
781
- this.once(SESSION_EVENT.ERROR, onError);
782
- this.once(SESSION_EVENT.CLOSE, onClose);
783
- });
784
- }
785
- // ─── Utilities ───────────────────────────────────────────────────────────
786
- getStats() {
787
- const ctxWindow = this.engineConfig.contextWindow ?? 200_000;
788
- return {
789
- turns: this._stats.turns,
790
- toolCalls: this._stats.toolCalls,
791
- toolErrors: this._stats.toolErrors,
792
- tokensIn: this._stats.tokensIn,
793
- tokensOut: this._stats.tokensOut,
794
- cachedTokens: this._stats.cachedTokens,
795
- costUsd: Math.round(this._stats.costUsd * 10000) / 10000,
796
- isReady: this._isReady,
797
- startTime: this._startTime,
798
- lastActivity: this._stats.lastActivity,
799
- contextPercent: this.engineConfig.persistent
800
- ? Math.min(100, Math.round(((this._stats.tokensIn + this._stats.tokensOut) / ctxWindow) * 100))
801
- : 0,
802
- sessionId: this.sessionId,
803
- uptime: this._startTime ? Math.round((Date.now() - new Date(this._startTime).getTime()) / 1000) : 0,
804
- };
805
- }
806
- getHistory(limit = DEFAULT_HISTORY_LIMIT) {
807
- return this._history.slice(-limit);
808
- }
809
- async compact(summary) {
810
- if (!this.engineConfig.persistent) {
811
- const event = {
812
- type: 'result',
813
- result: `${this.engineConfig.name} engine does not support compaction (one-shot mode)`,
814
- };
815
- return { text: event.result, event };
816
- }
817
- const msg = summary ? `/compact ${summary}` : '/compact';
818
- return this.send(msg, { waitForComplete: true, timeout: COMPACT_TIMEOUT_MS });
819
- }
820
- getEffort() {
821
- return this.options.effort || 'auto';
822
- }
823
- setEffort(level) {
824
- this.options.effort = level;
825
- }
826
- getCost() {
827
- const pricing = getModelPricing(this.options.model, this.engineConfig);
828
- const cachedPrice = pricing.cached ?? 0;
829
- const nonCachedIn = Math.max(0, this._stats.tokensIn - this._stats.cachedTokens);
830
- return {
831
- model: this.options.model || this.engineConfig.name,
832
- tokensIn: this._stats.tokensIn,
833
- tokensOut: this._stats.tokensOut,
834
- cachedTokens: this._stats.cachedTokens,
835
- pricing: { inputPer1M: pricing.input, outputPer1M: pricing.output, cachedPer1M: cachedPrice || undefined },
836
- breakdown: {
837
- inputCost: (nonCachedIn / 1_000_000) * pricing.input,
838
- cachedCost: (this._stats.cachedTokens / 1_000_000) * cachedPrice,
839
- outputCost: (this._stats.tokensOut / 1_000_000) * pricing.output,
840
- },
841
- totalUsd: this._stats.costUsd,
842
- };
843
- }
844
- resolveModel(alias) {
845
- return resolveAlias(alias);
846
- }
847
- pause() {
848
- this._isPaused = true;
849
- this.emit(SESSION_EVENT.PAUSED, { sessionId: this.sessionId });
850
- }
851
- resume() {
852
- this._isPaused = false;
853
- this.emit(SESSION_EVENT.RESUMED, { sessionId: this.sessionId });
854
- }
855
- stop() {
856
- // Persistent mode cleanup
857
- if (this._rl) {
858
- this._rl.close();
859
- this._rl = null;
860
- }
861
- if (this.proc) {
862
- const pid = this.proc.pid;
863
- this.proc.stdin?.end();
864
- this.proc.stdout?.destroy();
865
- this.proc.stderr?.destroy();
866
- try {
867
- process.kill(-pid, 'SIGTERM');
868
- }
869
- catch (err) {
870
- if (err.code !== 'ESRCH') {
871
- try {
872
- this.proc.kill('SIGTERM');
873
- }
874
- catch {
875
- /* ESRCH expected */
876
- }
877
- }
878
- }
879
- const p = this.proc;
880
- setTimeout(() => {
881
- try {
882
- process.kill(-pid, 'SIGKILL');
883
- }
884
- catch {
885
- /* ESRCH expected */
886
- }
887
- try {
888
- p.kill('SIGKILL');
889
- }
890
- catch {
891
- /* ESRCH expected */
892
- }
893
- }, STOP_SIGKILL_DELAY_MS);
894
- this.proc = null;
895
- }
896
- // One-shot mode cleanup
897
- if (this._currentRl) {
898
- this._currentRl.close();
899
- this._currentRl = null;
900
- }
901
- if (this.currentProc) {
902
- this.currentProc.stdin?.end();
903
- this.currentProc.stdout?.destroy();
904
- this.currentProc.stderr?.destroy();
905
- try {
906
- this.currentProc.kill('SIGTERM');
907
- }
908
- catch {
909
- /* ignore */
910
- }
911
- this.currentProc = null;
912
- }
913
- this._isReady = false;
914
- this._isPaused = false;
915
- this.emit(SESSION_EVENT.CLOSE, 143);
916
- }
917
- // ─── Private ─────────────────────────────────────────────────────────────
918
- _appendPermissionArgs(args) {
919
- const a = this.engineConfig.args;
920
- const mode = this.options.permissionMode;
921
- if (!mode || !a.permissionMode)
922
- return;
923
- // Map mode name if the engine uses different names
924
- const mapped = this.engineConfig.permissionModes?.[mode] ?? mode;
925
- args.push(a.permissionMode, mapped);
926
- }
927
- _updateCost() {
928
- const pricing = getModelPricing(this.options.model, this.engineConfig);
929
- const cachedPrice = pricing.cached ?? 0;
930
- const nonCachedIn = Math.max(0, this._stats.tokensIn - this._stats.cachedTokens);
931
- this._stats.costUsd =
932
- (nonCachedIn / 1_000_000) * pricing.input +
933
- (this._stats.cachedTokens / 1_000_000) * cachedPrice +
934
- (this._stats.tokensOut / 1_000_000) * pricing.output;
935
- }
936
- }
937
- //# sourceMappingURL=persistent-custom-session.js.map