@teamlearners/clawops 0.25.0 → 0.26.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.
@@ -242,6 +242,14 @@ declare class ClawOpsAgent {
242
242
  * multiple times — only the first invocation starts the task. Failures are
243
243
  * recorded in _prewarmFailed so the call-session path can fall back to start().
244
244
  */
245
+ /**
246
+ * 세션에 콜별 의존성(도구·내장도구·hold audio·recorder·logger)을 주입한다.
247
+ *
248
+ * prewarm 과 _startCallSession 양쪽에서 부른다. prewarm 은 세션이 LLM 에 보낼
249
+ * tool 스키마를 그 시점에 확정하므로(OpenAI session.update / Gemini live connect
250
+ * config), prewarm **전에** 최소 한 번은 도구가 들어가 있어야 한다.
251
+ */
252
+ private _injectSessionDeps;
245
253
  private _startPrewarm;
246
254
  private _handleRinging;
247
255
  private _handleFailed;
@@ -533,6 +541,11 @@ declare class OpenAIRealtime implements Session {
533
541
  private _greeting;
534
542
  private _log;
535
543
  private _builtinTools;
544
+ /**
545
+ * 마지막 session.update 로 LLM 에 알린 tool 이름들. attach() 가 이 값과 현재
546
+ * registry 를 비교해 달라졌을 때만(=MCP 도구 등 뒤늦게 붙은 경우) 재전송한다.
547
+ */
548
+ private _sentToolNames;
536
549
  setLogger(logger: Logger): void;
537
550
  setBuiltinTools(tools: Set<BuiltinTool>): void;
538
551
  getTelemetry(): SessionTelemetry;
@@ -562,6 +575,15 @@ declare class OpenAIRealtime implements Session {
562
575
  feedDtmf(digits: string): Promise<void>;
563
576
  feedAudio(audio: Buffer, timestamp?: number): void;
564
577
  stop(): Promise<void>;
578
+ /** Current tool schemas: user tools + builtin tools (flat realtime format). */
579
+ private _currentToolSchemas;
580
+ /**
581
+ * prewarm 이후 도구가 바뀌었으면 session.update 로 재전송한다.
582
+ *
583
+ * MCP 도구는 통화 시작 시점에 registry 에 붙으므로 prewarm 시점의 스키마에는
584
+ * 없다. 여기서 맞춰주지 않으면 발신 통화에서 MCP 도구를 영영 못 쓴다.
585
+ */
586
+ private _resyncTools;
565
587
  private _sendSessionUpdate;
566
588
  private _handleMessage;
567
589
  private _handleAudioDelta;
@@ -595,6 +617,13 @@ interface GeminiRealtimeOptions {
595
617
  realtimeInputConfig?: Record<string, unknown>;
596
618
  }
597
619
  declare class GeminiRealtime implements Session {
620
+ /**
621
+ * Gemini Live 는 connect 시점의 config 로 도구가 고정되어 세션 도중 변경할 수 없다.
622
+ *
623
+ * 통화 시작 시점에야 붙는 MCP 도구는 prewarm 된 세션에 넣을 방법이 없으므로,
624
+ * `ClawOpsAgent` 가 MCP 서버 설정과 이 플래그를 보고 prewarm 을 건너뛴다.
625
+ */
626
+ readonly toolsFrozenAfterPrewarm = true;
598
627
  private _apiKey;
599
628
  private _systemPrompt;
600
629
  private _model;
@@ -242,6 +242,14 @@ declare class ClawOpsAgent {
242
242
  * multiple times — only the first invocation starts the task. Failures are
243
243
  * recorded in _prewarmFailed so the call-session path can fall back to start().
244
244
  */
245
+ /**
246
+ * 세션에 콜별 의존성(도구·내장도구·hold audio·recorder·logger)을 주입한다.
247
+ *
248
+ * prewarm 과 _startCallSession 양쪽에서 부른다. prewarm 은 세션이 LLM 에 보낼
249
+ * tool 스키마를 그 시점에 확정하므로(OpenAI session.update / Gemini live connect
250
+ * config), prewarm **전에** 최소 한 번은 도구가 들어가 있어야 한다.
251
+ */
252
+ private _injectSessionDeps;
245
253
  private _startPrewarm;
246
254
  private _handleRinging;
247
255
  private _handleFailed;
@@ -533,6 +541,11 @@ declare class OpenAIRealtime implements Session {
533
541
  private _greeting;
534
542
  private _log;
535
543
  private _builtinTools;
544
+ /**
545
+ * 마지막 session.update 로 LLM 에 알린 tool 이름들. attach() 가 이 값과 현재
546
+ * registry 를 비교해 달라졌을 때만(=MCP 도구 등 뒤늦게 붙은 경우) 재전송한다.
547
+ */
548
+ private _sentToolNames;
536
549
  setLogger(logger: Logger): void;
537
550
  setBuiltinTools(tools: Set<BuiltinTool>): void;
538
551
  getTelemetry(): SessionTelemetry;
@@ -562,6 +575,15 @@ declare class OpenAIRealtime implements Session {
562
575
  feedDtmf(digits: string): Promise<void>;
563
576
  feedAudio(audio: Buffer, timestamp?: number): void;
564
577
  stop(): Promise<void>;
578
+ /** Current tool schemas: user tools + builtin tools (flat realtime format). */
579
+ private _currentToolSchemas;
580
+ /**
581
+ * prewarm 이후 도구가 바뀌었으면 session.update 로 재전송한다.
582
+ *
583
+ * MCP 도구는 통화 시작 시점에 registry 에 붙으므로 prewarm 시점의 스키마에는
584
+ * 없다. 여기서 맞춰주지 않으면 발신 통화에서 MCP 도구를 영영 못 쓴다.
585
+ */
586
+ private _resyncTools;
565
587
  private _sendSessionUpdate;
566
588
  private _handleMessage;
567
589
  private _handleAudioDelta;
@@ -595,6 +617,13 @@ interface GeminiRealtimeOptions {
595
617
  realtimeInputConfig?: Record<string, unknown>;
596
618
  }
597
619
  declare class GeminiRealtime implements Session {
620
+ /**
621
+ * Gemini Live 는 connect 시점의 config 로 도구가 고정되어 세션 도중 변경할 수 없다.
622
+ *
623
+ * 통화 시작 시점에야 붙는 MCP 도구는 prewarm 된 세션에 넣을 방법이 없으므로,
624
+ * `ClawOpsAgent` 가 MCP 서버 설정과 이 플래그를 보고 prewarm 을 건너뛴다.
625
+ */
626
+ readonly toolsFrozenAfterPrewarm = true;
598
627
  private _apiKey;
599
628
  private _systemPrompt;
600
629
  private _model;
@@ -1,6 +1,6 @@
1
- import { DEFAULT_BASE_URL, AgentError, AgentConnectionError, VERSION } from '../chunk-THZNZBP2.js';
2
- import { NOOP_LOGGER, resolveBuiltinTools, createAgentLogger, createPipelineLogger, BufferingCall, attachBuffered, ulawToPcm16, resamplePcm16, getBuiltinToolSchemas, BUILTIN_TOOL_NAMES, executeBuiltinTool, pcm16ToUlaw, applyUlawGain } from '../chunk-2BAYUQ7O.js';
3
- export { BUILTIN_TOOL_NAMES, BuiltinTool, DECODE_TABLE, createAgentLogger, createPipelineLogger, executeBuiltinTool, getBuiltinToolSchemas, isBuiltinTool, pcm16ToUlaw, resamplePcm16, ulawToPcm16 } from '../chunk-2BAYUQ7O.js';
1
+ import { DEFAULT_BASE_URL, AgentError, AgentConnectionError, VERSION } from '../chunk-WIA54H3X.js';
2
+ import { NOOP_LOGGER, resolveBuiltinTools, createAgentLogger, createPipelineLogger, BufferingCall, attachBuffered, ulawToPcm16, resamplePcm16, getBuiltinToolSchemas, BUILTIN_TOOL_NAMES, executeBuiltinTool, CALL_NOT_READY_RESULT, pcm16ToUlaw, applyUlawGain } from '../chunk-WKGTY7QT.js';
3
+ export { BUILTIN_TOOL_NAMES, BuiltinTool, DECODE_TABLE, createAgentLogger, createPipelineLogger, executeBuiltinTool, getBuiltinToolSchemas, isBuiltinTool, pcm16ToUlaw, resamplePcm16, ulawToPcm16 } from '../chunk-WKGTY7QT.js';
4
4
  import * as fs from 'fs';
5
5
  import * as path from 'path';
6
6
  import os from 'os';
@@ -1697,10 +1697,43 @@ var ClawOpsAgent = class _ClawOpsAgent {
1697
1697
  * multiple times — only the first invocation starts the task. Failures are
1698
1698
  * recorded in _prewarmFailed so the call-session path can fall back to start().
1699
1699
  */
1700
+ /**
1701
+ * 세션에 콜별 의존성(도구·내장도구·hold audio·recorder·logger)을 주입한다.
1702
+ *
1703
+ * prewarm 과 _startCallSession 양쪽에서 부른다. prewarm 은 세션이 LLM 에 보낼
1704
+ * tool 스키마를 그 시점에 확정하므로(OpenAI session.update / Gemini live connect
1705
+ * config), prewarm **전에** 최소 한 번은 도구가 들어가 있어야 한다.
1706
+ */
1707
+ _injectSessionDeps(tools, recorder) {
1708
+ const sessionHandler = this._session;
1709
+ if ("setToolRegistry" in sessionHandler && typeof sessionHandler.setToolRegistry === "function") {
1710
+ sessionHandler.setToolRegistry(tools);
1711
+ }
1712
+ if (recorder && "setRecorder" in sessionHandler && typeof sessionHandler.setRecorder === "function") {
1713
+ sessionHandler.setRecorder(recorder);
1714
+ }
1715
+ if ("setBuiltinTools" in sessionHandler && typeof sessionHandler.setBuiltinTools === "function") {
1716
+ sessionHandler.setBuiltinTools(this._builtinTools);
1717
+ }
1718
+ if ("setLogger" in sessionHandler && typeof sessionHandler.setLogger === "function") {
1719
+ sessionHandler.setLogger(this._isPipelineSession ? this._pipelineLog : this._log);
1720
+ }
1721
+ if (this._holdAudioChunks && "setHoldAudio" in sessionHandler && typeof sessionHandler.setHoldAudio === "function") {
1722
+ sessionHandler.setHoldAudio(this._holdAudioChunks);
1723
+ }
1724
+ }
1700
1725
  _startPrewarm(callId) {
1701
1726
  if (this._prewarmTasks.has(callId)) return;
1702
1727
  const sessionHandler = this._session;
1703
1728
  if (typeof sessionHandler.prewarm !== "function") return;
1729
+ if (this._mcpServers.length > 0 && sessionHandler.toolsFrozenAfterPrewarm) {
1730
+ this._log.info(
1731
+ { callId },
1732
+ "Skipping prewarm \u2014 \uC138\uC158\uC774 \uC5F0\uACB0 \uD6C4 \uB3C4\uAD6C \uBCC0\uACBD\uC744 \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uC544 MCP \uB3C4\uAD6C\uAC00 \uB204\uB77D\uB41C\uB2E4."
1733
+ );
1734
+ return;
1735
+ }
1736
+ this._injectSessionDeps(this._tools.fork());
1704
1737
  const PREWARM_TIMEOUT_MS = 1e4;
1705
1738
  const t0 = Date.now();
1706
1739
  this._log.info(`[PREWARM-T] start call_id=${callId} t=${(t0 / 1e3).toFixed(3)}`);
@@ -1866,21 +1899,7 @@ var ClawOpsAgent = class _ClawOpsAgent {
1866
1899
  session._waitForMark = (name, timeoutMs) => mediaWs.waitForMark(name, timeoutMs);
1867
1900
  session._flushTransport = () => mediaWs.flush();
1868
1901
  const sessionHandler = this._session;
1869
- if ("setToolRegistry" in sessionHandler && typeof sessionHandler.setToolRegistry === "function") {
1870
- sessionHandler.setToolRegistry(sessionTools);
1871
- }
1872
- if (recorder && "setRecorder" in sessionHandler && typeof sessionHandler.setRecorder === "function") {
1873
- sessionHandler.setRecorder(recorder);
1874
- }
1875
- if ("setBuiltinTools" in sessionHandler && typeof sessionHandler.setBuiltinTools === "function") {
1876
- sessionHandler.setBuiltinTools(this._builtinTools);
1877
- }
1878
- if ("setLogger" in sessionHandler && typeof sessionHandler.setLogger === "function") {
1879
- sessionHandler.setLogger(this._isPipelineSession ? this._pipelineLog : this._log);
1880
- }
1881
- if (this._holdAudioChunks && "setHoldAudio" in sessionHandler && typeof sessionHandler.setHoldAudio === "function") {
1882
- sessionHandler.setHoldAudio(this._holdAudioChunks);
1883
- }
1902
+ this._injectSessionDeps(sessionTools, recorder);
1884
1903
  this._callSessions.set(session.callId, sessionHandler);
1885
1904
  mediaWs.onAudio((ulawAudio, timestamp) => {
1886
1905
  latestMediaTs = timestamp;
@@ -2208,6 +2227,17 @@ var PipelineSession = class {
2208
2227
  return;
2209
2228
  }
2210
2229
  }
2230
+ if (BUILTIN_TOOL_NAMES.has(name) && this._callSession instanceof BufferingCall) {
2231
+ this._log.warn("Builtin tool %s called before answer \u2014 deferring", name);
2232
+ this._conversation.push({
2233
+ role: "tool",
2234
+ content: CALL_NOT_READY_RESULT,
2235
+ tool_call_id: id,
2236
+ name
2237
+ });
2238
+ await this._respond();
2239
+ return;
2240
+ }
2211
2241
  if (!this._tools) return;
2212
2242
  this._callSession?.recordToolCall();
2213
2243
  const player = this._holdAudioChunks && this._callSession && !(this._callSession instanceof BufferingCall) ? new HoldAudioPlayer(this._callSession, this._holdAudioChunks) : null;
@@ -2291,6 +2321,11 @@ var OpenAIRealtime = class {
2291
2321
  _greeting;
2292
2322
  _log = NOOP_LOGGER;
2293
2323
  _builtinTools = null;
2324
+ /**
2325
+ * 마지막 session.update 로 LLM 에 알린 tool 이름들. attach() 가 이 값과 현재
2326
+ * registry 를 비교해 달라졌을 때만(=MCP 도구 등 뒤늦게 붙은 경우) 재전송한다.
2327
+ */
2328
+ _sentToolNames = null;
2294
2329
  setLogger(logger) {
2295
2330
  this._log = logger;
2296
2331
  }
@@ -2400,6 +2435,7 @@ var OpenAIRealtime = class {
2400
2435
  }
2401
2436
  /** Attach a real CallSession to the prewarmed session and flush buffered audio. */
2402
2437
  async attach(callSession) {
2438
+ this._resyncTools();
2403
2439
  const prev = this._call;
2404
2440
  this._call = callSession;
2405
2441
  attachBuffered(prev, callSession);
@@ -2460,10 +2496,36 @@ var OpenAIRealtime = class {
2460
2496
  });
2461
2497
  }
2462
2498
  }
2463
- _sendSessionUpdate() {
2464
- if (!this._ws || this._ws.readyState !== 1) return;
2499
+ /** Current tool schemas: user tools + builtin tools (flat realtime format). */
2500
+ _currentToolSchemas() {
2465
2501
  const toolSchemas = this._tools ? this._tools.toOpenAITools().map((t) => ({ type: "function", ...t.function })) : [];
2466
2502
  toolSchemas.push(...getBuiltinToolSchemas(this._builtinTools, "realtime"));
2503
+ return toolSchemas;
2504
+ }
2505
+ /**
2506
+ * prewarm 이후 도구가 바뀌었으면 session.update 로 재전송한다.
2507
+ *
2508
+ * MCP 도구는 통화 시작 시점에 registry 에 붙으므로 prewarm 시점의 스키마에는
2509
+ * 없다. 여기서 맞춰주지 않으면 발신 통화에서 MCP 도구를 영영 못 쓴다.
2510
+ */
2511
+ _resyncTools() {
2512
+ if (!this._ws || this._ws.readyState !== 1) return;
2513
+ const toolSchemas = this._currentToolSchemas();
2514
+ const names = toolSchemas.map((t) => String(t["name"] ?? ""));
2515
+ if (this._sentToolNames && names.length === this._sentToolNames.length && names.every((n, i) => n === this._sentToolNames[i])) {
2516
+ return;
2517
+ }
2518
+ this._send({
2519
+ type: "session.update",
2520
+ session: { type: "realtime", tools: toolSchemas }
2521
+ });
2522
+ this._sentToolNames = names;
2523
+ this._log.info("Tool schema resynced after prewarm: %s", names.join(", "));
2524
+ }
2525
+ _sendSessionUpdate() {
2526
+ if (!this._ws || this._ws.readyState !== 1) return;
2527
+ const toolSchemas = this._currentToolSchemas();
2528
+ this._sentToolNames = toolSchemas.map((t) => String(t["name"] ?? ""));
2467
2529
  this._send({
2468
2530
  type: "session.update",
2469
2531
  session: {
@@ -2627,6 +2689,20 @@ var OpenAIRealtime = class {
2627
2689
  return;
2628
2690
  }
2629
2691
  }
2692
+ if (BUILTIN_TOOL_NAMES.has(funcName) && this._call instanceof BufferingCall) {
2693
+ this._log.warn("Builtin tool %s called before answer \u2014 deferring", funcName);
2694
+ await this._waitForResponseDone();
2695
+ this._send({
2696
+ type: "conversation.item.create",
2697
+ item: {
2698
+ type: "function_call_output",
2699
+ call_id: callId,
2700
+ output: CALL_NOT_READY_RESULT
2701
+ }
2702
+ });
2703
+ this._send({ type: "response.create" });
2704
+ return;
2705
+ }
2630
2706
  if (!this._tools || !this._tools.has(funcName)) {
2631
2707
  this._log.error("Unknown tool: %s", funcName);
2632
2708
  await this._waitForResponseDone();
@@ -2764,6 +2840,13 @@ function sanitizeSchemaForGemini(schema, defs, depth = 0) {
2764
2840
  return result;
2765
2841
  }
2766
2842
  var GeminiRealtime = class _GeminiRealtime {
2843
+ /**
2844
+ * Gemini Live 는 connect 시점의 config 로 도구가 고정되어 세션 도중 변경할 수 없다.
2845
+ *
2846
+ * 통화 시작 시점에야 붙는 MCP 도구는 prewarm 된 세션에 넣을 방법이 없으므로,
2847
+ * `ClawOpsAgent` 가 MCP 서버 설정과 이 플래그를 보고 prewarm 을 건너뛴다.
2848
+ */
2849
+ toolsFrozenAfterPrewarm = true;
2767
2850
  _apiKey;
2768
2851
  _systemPrompt;
2769
2852
  _model;
@@ -3069,6 +3152,11 @@ var GeminiRealtime = class _GeminiRealtime {
3069
3152
  continue;
3070
3153
  }
3071
3154
  }
3155
+ if (BUILTIN_TOOL_NAMES.has(name) && this._call instanceof BufferingCall) {
3156
+ this._log.warn("Builtin tool %s called before answer \u2014 deferring", name);
3157
+ responses.push({ id: fcId, name, response: { result: CALL_NOT_READY_RESULT } });
3158
+ continue;
3159
+ }
3072
3160
  if (!this._tools || !this._tools.has(name)) {
3073
3161
  this._log.error("Unknown tool: %s", name);
3074
3162
  responses.push({ id: fcId, name, response: { error: `Unknown tool: ${name}` } });