@teamlearners/clawops 0.31.0 → 0.32.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
- import { F as FunctionTool, S as Session, B as BuiltinTool, C as CallSession, a as STT, L as LLM, T as TTS, b as ToolRegistry, c as SessionTelemetry, d as SpeechEvent, e as ConversationMessage, f as LLMChunk } from '../base-CINMfkOD.cjs';
2
- export { g as CallDirection, h as CallStatus, O as OpenAIToolDefinition, i as ToolCallRequest, j as functionTool, z as zodToToolParams } from '../base-CINMfkOD.cjs';
1
+ import { F as FunctionTool, S as Session, B as BuiltinTool, C as CallSession, a as STT, L as LLM, T as TTS, b as ToolRegistry, c as SessionTelemetry, d as SpeechEvent, e as ConversationMessage, f as LLMChunk } from '../base-B6S9WQRN.cjs';
2
+ export { g as CallDirection, h as CallStatus, O as OpenAIToolDefinition, i as ToolCallRequest, j as functionTool, z as zodToToolParams } from '../base-B6S9WQRN.cjs';
3
3
  import { Logger } from 'pino';
4
4
  export { Logger } from 'pino';
5
5
  import 'zod';
@@ -164,6 +164,8 @@ declare class ClawOpsAgent {
164
164
  private _recording;
165
165
  private _recordingPath;
166
166
  private _activeSessions;
167
+ /** 미디어 정리를 마친 통화가 서버 종료 프레임을 기다리는 자리. callId → resolve. */
168
+ private _terminalWaiters;
167
169
  private _builtinTools;
168
170
  private _passiveDtmfDebounceMs;
169
171
  private _passiveDtmfBuffer;
@@ -224,6 +226,17 @@ declare class ClawOpsAgent {
224
226
  machineDetection?: 'Enable' | 'Hangup';
225
227
  }): Promise<CallSession>;
226
228
  private _handleIncoming;
229
+ /**
230
+ * 서버 종료 프레임을 짧게 기다린다. 미디어 정리 직후에만 부른다.
231
+ *
232
+ * 왜 기다리나: `call_end` 는 인바운드 사용자가 통화 결과를 받는 **유일한 통로**인데, 서버는
233
+ * 미디어 WS 를 먼저 닫고 자원 정리를 마친 뒤에야 control 종료 프레임을 보낸다. 안 기다리면
234
+ * 그 핸들러 안에서 `endedDuration` 은 영영 null 이다.
235
+ *
236
+ * 상한을 다 쓰는 경우는 제어 연결이 죽어 프레임이 아예 안 오는 때뿐이다 — 그 프레임은
237
+ * 예전부터 늘 오던 것이고(이번에 바뀐 건 내용뿐), 정상 경로에서는 밀리초 안에 풀린다.
238
+ */
239
+ private _awaitServerTerminal;
227
240
  private _handleEnded;
228
241
  /**
229
242
  * Drop prewarm bookkeeping for a callId. Used on hangup/failure paths.
@@ -1,5 +1,5 @@
1
- import { F as FunctionTool, S as Session, B as BuiltinTool, C as CallSession, a as STT, L as LLM, T as TTS, b as ToolRegistry, c as SessionTelemetry, d as SpeechEvent, e as ConversationMessage, f as LLMChunk } from '../base-CINMfkOD.js';
2
- export { g as CallDirection, h as CallStatus, O as OpenAIToolDefinition, i as ToolCallRequest, j as functionTool, z as zodToToolParams } from '../base-CINMfkOD.js';
1
+ import { F as FunctionTool, S as Session, B as BuiltinTool, C as CallSession, a as STT, L as LLM, T as TTS, b as ToolRegistry, c as SessionTelemetry, d as SpeechEvent, e as ConversationMessage, f as LLMChunk } from '../base-B6S9WQRN.js';
2
+ export { g as CallDirection, h as CallStatus, O as OpenAIToolDefinition, i as ToolCallRequest, j as functionTool, z as zodToToolParams } from '../base-B6S9WQRN.js';
3
3
  import { Logger } from 'pino';
4
4
  export { Logger } from 'pino';
5
5
  import 'zod';
@@ -164,6 +164,8 @@ declare class ClawOpsAgent {
164
164
  private _recording;
165
165
  private _recordingPath;
166
166
  private _activeSessions;
167
+ /** 미디어 정리를 마친 통화가 서버 종료 프레임을 기다리는 자리. callId → resolve. */
168
+ private _terminalWaiters;
167
169
  private _builtinTools;
168
170
  private _passiveDtmfDebounceMs;
169
171
  private _passiveDtmfBuffer;
@@ -224,6 +226,17 @@ declare class ClawOpsAgent {
224
226
  machineDetection?: 'Enable' | 'Hangup';
225
227
  }): Promise<CallSession>;
226
228
  private _handleIncoming;
229
+ /**
230
+ * 서버 종료 프레임을 짧게 기다린다. 미디어 정리 직후에만 부른다.
231
+ *
232
+ * 왜 기다리나: `call_end` 는 인바운드 사용자가 통화 결과를 받는 **유일한 통로**인데, 서버는
233
+ * 미디어 WS 를 먼저 닫고 자원 정리를 마친 뒤에야 control 종료 프레임을 보낸다. 안 기다리면
234
+ * 그 핸들러 안에서 `endedDuration` 은 영영 null 이다.
235
+ *
236
+ * 상한을 다 쓰는 경우는 제어 연결이 죽어 프레임이 아예 안 오는 때뿐이다 — 그 프레임은
237
+ * 예전부터 늘 오던 것이고(이번에 바뀐 건 내용뿐), 정상 경로에서는 밀리초 안에 풀린다.
238
+ */
239
+ private _awaitServerTerminal;
227
240
  private _handleEnded;
228
241
  /**
229
242
  * Drop prewarm bookkeeping for a callId. Used on hangup/failure paths.
@@ -1,4 +1,4 @@
1
- import { DEFAULT_BASE_URL, AgentError, AgentConnectionError, VERSION } from '../chunk-LBPPO62W.js';
1
+ import { DEFAULT_BASE_URL, AgentError, AgentConnectionError, VERSION } from '../chunk-MXJ2LM6R.js';
2
2
  import { NOOP_LOGGER, resolveBuiltinTools, createAgentLogger, createPipelineLogger, BufferingCall, attachBuffered, ulawToPcm16, resamplePcm16, getBuiltinToolSchemas, BUILTIN_TOOL_NAMES, executeBuiltinTool, CALL_NOT_READY_RESULT, pcm16ToUlaw, applyUlawGain } from '../chunk-4YLSDMLL.js';
3
3
  export { BUILTIN_TOOL_NAMES, BuiltinTool, DECODE_TABLE, createAgentLogger, createPipelineLogger, executeBuiltinTool, getBuiltinToolSchemas, isBuiltinTool, pcm16ToUlaw, resamplePcm16, ulawToPcm16 } from '../chunk-4YLSDMLL.js';
4
4
  import * as fs from 'fs';
@@ -740,6 +740,7 @@ var CallSession = class {
740
740
  metadata;
741
741
  _status;
742
742
  _endedStatus = null;
743
+ _endedDuration = null;
743
744
  _sendAudioFn = null;
744
745
  _clearAudioFn = null;
745
746
  _hangupFn = null;
@@ -799,9 +800,22 @@ var CallSession = class {
799
800
  get endedStatus() {
800
801
  return this._endedStatus;
801
802
  }
803
+ /**
804
+ * 서버가 확정한 통화 시간(초). 통화가 끝나기 전에는 `null`.
805
+ *
806
+ * ⚠️ `duration` 과 다르다 — 그쪽은 SDK 가 로컬 시계로 재는 **경과 시간**이라 통화 중에도
807
+ * 읽히고, 세션이 붙기 전후의 오차를 포함한다. 기록·정산에 쓸 값은 이쪽이다.
808
+ */
809
+ get endedDuration() {
810
+ return this._endedDuration;
811
+ }
802
812
  get duration() {
803
813
  return (Date.now() - this.startTime.getTime()) / 1e3;
804
814
  }
815
+ /** @internal 서버가 통보한 통화 시간을 확정한다. */
816
+ _setEndedDuration(seconds) {
817
+ this._endedDuration = seconds;
818
+ }
805
819
  get metrics() {
806
820
  return this._metrics;
807
821
  }
@@ -1391,6 +1405,7 @@ var ATTR_CALL_DIRECTION = "clawops.call.direction";
1391
1405
  var ATTR_AGENT_ID = "clawops.agent.id";
1392
1406
 
1393
1407
  // src/agent/agent.ts
1408
+ var TERMINAL_FRAME_GRACE_MS = 2e3;
1394
1409
  var ClawOpsAgent = class _ClawOpsAgent {
1395
1410
  _apiKey;
1396
1411
  _accountId;
@@ -1404,6 +1419,8 @@ var ClawOpsAgent = class _ClawOpsAgent {
1404
1419
  _recording;
1405
1420
  _recordingPath;
1406
1421
  _activeSessions = /* @__PURE__ */ new Map();
1422
+ /** 미디어 정리를 마친 통화가 서버 종료 프레임을 기다리는 자리. callId → resolve. */
1423
+ _terminalWaiters = /* @__PURE__ */ new Map();
1407
1424
  _builtinTools;
1408
1425
  _passiveDtmfDebounceMs;
1409
1426
  _passiveDtmfBuffer = [];
@@ -1553,6 +1570,7 @@ var ClawOpsAgent = class _ClawOpsAgent {
1553
1570
  this._controlWs.close();
1554
1571
  this._controlWs = null;
1555
1572
  }
1573
+ for (const wake of [...this._terminalWaiters.values()]) wake();
1556
1574
  for (const session of this._activeSessions.values()) {
1557
1575
  session._markEnded();
1558
1576
  }
@@ -1640,12 +1658,43 @@ var ClawOpsAgent = class _ClawOpsAgent {
1640
1658
  this._safeStartCallSession(session, mediaUrl, callId);
1641
1659
  }
1642
1660
  }
1661
+ /**
1662
+ * 서버 종료 프레임을 짧게 기다린다. 미디어 정리 직후에만 부른다.
1663
+ *
1664
+ * 왜 기다리나: `call_end` 는 인바운드 사용자가 통화 결과를 받는 **유일한 통로**인데, 서버는
1665
+ * 미디어 WS 를 먼저 닫고 자원 정리를 마친 뒤에야 control 종료 프레임을 보낸다. 안 기다리면
1666
+ * 그 핸들러 안에서 `endedDuration` 은 영영 null 이다.
1667
+ *
1668
+ * 상한을 다 쓰는 경우는 제어 연결이 죽어 프레임이 아예 안 오는 때뿐이다 — 그 프레임은
1669
+ * 예전부터 늘 오던 것이고(이번에 바뀐 건 내용뿐), 정상 경로에서는 밀리초 안에 풀린다.
1670
+ */
1671
+ async _awaitServerTerminal(session) {
1672
+ if (session.endedDuration !== null) return;
1673
+ await new Promise((resolve) => {
1674
+ const done = () => {
1675
+ clearTimeout(timer);
1676
+ this._terminalWaiters.delete(session.callId);
1677
+ resolve();
1678
+ };
1679
+ const timer = setTimeout(() => {
1680
+ this._log.debug(
1681
+ "\uC11C\uBC84 \uC885\uB8CC \uD504\uB808\uC784\uC774 %dms \uC548\uC5D0 \uC624\uC9C0 \uC54A\uC558\uB2E4 \u2014 endedDuration \uC5C6\uC774 \uC9C4\uD589: %s",
1682
+ TERMINAL_FRAME_GRACE_MS,
1683
+ session.callId
1684
+ );
1685
+ done();
1686
+ }, TERMINAL_FRAME_GRACE_MS);
1687
+ this._terminalWaiters.set(session.callId, done);
1688
+ });
1689
+ }
1643
1690
  _handleEnded(event) {
1644
1691
  const callId = event["callId"];
1645
1692
  const status = event["status"] || "completed";
1693
+ const endedDuration = event["duration"];
1646
1694
  const session = this._activeSessions.get(callId);
1647
1695
  if (session) {
1648
1696
  this._log.info("Call ended (server): %s (status=%s)", callId, status);
1697
+ if (typeof endedDuration === "number") session._setEndedDuration(endedDuration);
1649
1698
  if (status !== "completed") {
1650
1699
  this._log.info("Outbound call not connected: %s (%s)", callId, status);
1651
1700
  session._emit("call_failed", status);
@@ -1653,6 +1702,7 @@ var ClawOpsAgent = class _ClawOpsAgent {
1653
1702
  session._markEnded(status);
1654
1703
  this._activeSessions.delete(callId);
1655
1704
  }
1705
+ this._terminalWaiters.get(callId)?.();
1656
1706
  void this._cleanupPrewarm(callId);
1657
1707
  }
1658
1708
  /**
@@ -2007,6 +2057,7 @@ var ClawOpsAgent = class _ClawOpsAgent {
2007
2057
  this._controlWs?.send({ event: "call.metrics", callId: session.callId, metrics: session.metrics });
2008
2058
  } catch {
2009
2059
  }
2060
+ await this._awaitServerTerminal(session);
2010
2061
  session._emit("call_end");
2011
2062
  session._markEnded();
2012
2063
  this._activeSessions.delete(session.callId);