@kodelyth/voice-call 2026.5.42 → 2026.6.2

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 (111) hide show
  1. package/package.json +18 -6
  2. package/api.ts +0 -16
  3. package/cli-metadata.ts +0 -10
  4. package/config-api.ts +0 -12
  5. package/index.test.ts +0 -1075
  6. package/index.ts +0 -863
  7. package/runtime-api.ts +0 -20
  8. package/runtime-entry.ts +0 -1
  9. package/setup-api.ts +0 -47
  10. package/src/allowlist.test.ts +0 -18
  11. package/src/allowlist.ts +0 -19
  12. package/src/cli.test.ts +0 -12
  13. package/src/cli.ts +0 -866
  14. package/src/config-compat.test.ts +0 -130
  15. package/src/config-compat.ts +0 -227
  16. package/src/config.test.ts +0 -542
  17. package/src/config.ts +0 -883
  18. package/src/core-bridge.ts +0 -14
  19. package/src/deep-merge.test.ts +0 -40
  20. package/src/deep-merge.ts +0 -23
  21. package/src/gateway-continue-operation.ts +0 -200
  22. package/src/http-headers.test.ts +0 -16
  23. package/src/http-headers.ts +0 -15
  24. package/src/manager/context.ts +0 -50
  25. package/src/manager/events.test.ts +0 -578
  26. package/src/manager/events.ts +0 -332
  27. package/src/manager/lifecycle.ts +0 -53
  28. package/src/manager/lookup.test.ts +0 -52
  29. package/src/manager/lookup.ts +0 -35
  30. package/src/manager/outbound.test.ts +0 -629
  31. package/src/manager/outbound.ts +0 -508
  32. package/src/manager/state.ts +0 -48
  33. package/src/manager/store.ts +0 -107
  34. package/src/manager/timers.test.ts +0 -127
  35. package/src/manager/timers.ts +0 -113
  36. package/src/manager/twiml.test.ts +0 -13
  37. package/src/manager/twiml.ts +0 -17
  38. package/src/manager.closed-loop.test.ts +0 -259
  39. package/src/manager.inbound-allowlist.test.ts +0 -183
  40. package/src/manager.notify.test.ts +0 -390
  41. package/src/manager.restore.test.ts +0 -310
  42. package/src/manager.test-harness.ts +0 -127
  43. package/src/manager.ts +0 -441
  44. package/src/media-stream.test.ts +0 -953
  45. package/src/media-stream.ts +0 -876
  46. package/src/providers/base.ts +0 -99
  47. package/src/providers/mock.test.ts +0 -86
  48. package/src/providers/mock.ts +0 -185
  49. package/src/providers/plivo.test.ts +0 -93
  50. package/src/providers/plivo.ts +0 -601
  51. package/src/providers/shared/call-status.test.ts +0 -24
  52. package/src/providers/shared/call-status.ts +0 -24
  53. package/src/providers/shared/guarded-json-api.test.ts +0 -127
  54. package/src/providers/shared/guarded-json-api.ts +0 -49
  55. package/src/providers/telnyx.test.ts +0 -489
  56. package/src/providers/telnyx.ts +0 -419
  57. package/src/providers/twilio/api.test.ts +0 -184
  58. package/src/providers/twilio/api.ts +0 -100
  59. package/src/providers/twilio/twiml-policy.test.ts +0 -84
  60. package/src/providers/twilio/twiml-policy.ts +0 -87
  61. package/src/providers/twilio/webhook.ts +0 -34
  62. package/src/providers/twilio.test.ts +0 -607
  63. package/src/providers/twilio.ts +0 -861
  64. package/src/providers/twilio.types.ts +0 -17
  65. package/src/realtime-agent-context.test.ts +0 -101
  66. package/src/realtime-agent-context.ts +0 -149
  67. package/src/realtime-defaults.ts +0 -3
  68. package/src/realtime-fast-context.test.ts +0 -74
  69. package/src/realtime-fast-context.ts +0 -27
  70. package/src/realtime-transcription.runtime.ts +0 -4
  71. package/src/realtime-voice.runtime.ts +0 -5
  72. package/src/response-generator.test.ts +0 -385
  73. package/src/response-generator.ts +0 -348
  74. package/src/response-model.test.ts +0 -71
  75. package/src/response-model.ts +0 -23
  76. package/src/runtime.test.ts +0 -625
  77. package/src/runtime.ts +0 -528
  78. package/src/telephony-audio.test.ts +0 -61
  79. package/src/telephony-audio.ts +0 -12
  80. package/src/telephony-tts.test.ts +0 -196
  81. package/src/telephony-tts.ts +0 -235
  82. package/src/test-fixtures.ts +0 -82
  83. package/src/tts-provider-voice.test.ts +0 -34
  84. package/src/tts-provider-voice.ts +0 -21
  85. package/src/tunnel.test.ts +0 -173
  86. package/src/tunnel.ts +0 -314
  87. package/src/types.ts +0 -311
  88. package/src/utils.test.ts +0 -17
  89. package/src/utils.ts +0 -14
  90. package/src/voice-mapping.test.ts +0 -32
  91. package/src/voice-mapping.ts +0 -65
  92. package/src/webhook/realtime-audio-pacer.test.ts +0 -146
  93. package/src/webhook/realtime-audio-pacer.ts +0 -204
  94. package/src/webhook/realtime-handler.test.ts +0 -1450
  95. package/src/webhook/realtime-handler.ts +0 -1382
  96. package/src/webhook/stale-call-reaper.test.ts +0 -89
  97. package/src/webhook/stale-call-reaper.ts +0 -38
  98. package/src/webhook/stream-frame-adapter.test.ts +0 -187
  99. package/src/webhook/stream-frame-adapter.ts +0 -219
  100. package/src/webhook/tailscale.test.ts +0 -216
  101. package/src/webhook/tailscale.ts +0 -129
  102. package/src/webhook-exposure.test.ts +0 -33
  103. package/src/webhook-exposure.ts +0 -84
  104. package/src/webhook-security.test.ts +0 -813
  105. package/src/webhook-security.ts +0 -982
  106. package/src/webhook.hangup-once.lifecycle.test.ts +0 -179
  107. package/src/webhook.test.ts +0 -1615
  108. package/src/webhook.ts +0 -933
  109. package/src/webhook.types.ts +0 -5
  110. package/src/websocket-test-support.ts +0 -72
  111. package/tsconfig.json +0 -16
@@ -1,17 +0,0 @@
1
- import type { WebhookSecurityConfig } from "../config.js";
2
-
3
- /**
4
- * Twilio Voice API provider options.
5
- */
6
- export interface TwilioProviderOptions {
7
- /** Allow ngrok free tier compatibility mode (loopback only, less secure) */
8
- allowNgrokFreeTierLoopbackBypass?: boolean;
9
- /** Override public URL for signature verification */
10
- publicUrl?: string;
11
- /** Path for media stream WebSocket (e.g., /voice/stream) */
12
- streamPath?: string;
13
- /** Skip webhook signature verification (development only) */
14
- skipVerification?: boolean;
15
- /** Webhook security options (forwarded headers/allowlist) */
16
- webhookSecurity?: WebhookSecurityConfig;
17
- }
@@ -1,101 +0,0 @@
1
- import { mkdtemp, rm, writeFile } from "node:fs/promises";
2
- import { tmpdir } from "node:os";
3
- import path from "node:path";
4
- import { afterEach, describe, expect, it, vi } from "vitest";
5
- import type { VoiceCallConfig } from "./config.js";
6
- import type { CoreAgentDeps, CoreConfig } from "./core-bridge.js";
7
- import { buildRealtimeVoiceInstructions } from "./realtime-agent-context.js";
8
- import { createVoiceCallBaseConfig } from "./test-fixtures.js";
9
-
10
- const tempDirs: string[] = [];
11
-
12
- afterEach(async () => {
13
- await Promise.all(tempDirs.splice(0).map((dir) => rm(dir, { recursive: true, force: true })));
14
- });
15
-
16
- async function createWorkspace(): Promise<string> {
17
- const workspaceDir = await mkdtemp(path.join(tmpdir(), "klaw-voice-context-"));
18
- tempDirs.push(workspaceDir);
19
- return workspaceDir;
20
- }
21
-
22
- function createConfig(overrides?: Partial<VoiceCallConfig["realtime"]>): VoiceCallConfig {
23
- const config = createVoiceCallBaseConfig();
24
- config.agentId = "voice";
25
- config.realtime.enabled = true;
26
- config.realtime.instructions = "Base voice instructions.";
27
- config.realtime = {
28
- ...config.realtime,
29
- ...overrides,
30
- fastContext: {
31
- ...config.realtime.fastContext,
32
- ...overrides?.fastContext,
33
- sources: overrides?.fastContext?.sources ?? config.realtime.fastContext.sources,
34
- },
35
- agentContext: {
36
- ...config.realtime.agentContext,
37
- ...overrides?.agentContext,
38
- files: overrides?.agentContext?.files ?? config.realtime.agentContext.files,
39
- },
40
- tools: overrides?.tools ?? config.realtime.tools,
41
- providers: overrides?.providers ?? config.realtime.providers,
42
- };
43
- return config;
44
- }
45
-
46
- function createAgentRuntime(workspaceDir: string): CoreAgentDeps {
47
- return {
48
- resolveAgentIdentity: vi.fn(() => ({
49
- name: "Claw Voice",
50
- emoji: ":claw:",
51
- theme: "bright",
52
- vibe: "snappy",
53
- creature: "operator",
54
- })),
55
- resolveAgentWorkspaceDir: vi.fn(() => workspaceDir),
56
- } as unknown as CoreAgentDeps;
57
- }
58
-
59
- describe("buildRealtimeVoiceInstructions", () => {
60
- it("injects bounded identity, system prompt, and workspace context", async () => {
61
- const workspaceDir = await createWorkspace();
62
- await writeFile(path.join(workspaceDir, "SOUL.md"), "Stay quick, direct, and warm.\n");
63
- await writeFile(path.join(workspaceDir, "IDENTITY.md"), "Name: Claw Voice\nVibe: snappy\n");
64
- await writeFile(path.join(workspaceDir, "SECRET.md"), "do not include\n");
65
-
66
- const coreConfig = {
67
- agents: {
68
- list: [{ id: "voice", systemPromptOverride: "Keep spoken answers short." }],
69
- },
70
- } as CoreConfig;
71
-
72
- const instructions = await buildRealtimeVoiceInstructions({
73
- baseInstructions: "Base voice instructions.",
74
- config: createConfig({
75
- consultPolicy: "substantive",
76
- agentContext: {
77
- enabled: true,
78
- maxChars: 2000,
79
- includeIdentity: true,
80
- includeSystemPrompt: true,
81
- includeWorkspaceFiles: true,
82
- files: ["SOUL.md", "IDENTITY.md", "../SECRET.md"],
83
- },
84
- }),
85
- coreConfig,
86
- agentRuntime: createAgentRuntime(workspaceDir),
87
- });
88
-
89
- expect(instructions).toContain("Klaw agent voice context:");
90
- expect(instructions).toContain("Consult behavior:");
91
- expect(instructions).toContain("Call klaw_agent_consult before answering requests");
92
- expect(instructions).toContain("- Agent id: voice");
93
- expect(instructions).toContain("- Name: Claw Voice");
94
- expect(instructions).toContain("- Vibe: snappy");
95
- expect(instructions).toContain("Keep spoken answers short.");
96
- expect(instructions).toContain("### SOUL.md");
97
- expect(instructions).toContain("Stay quick, direct, and warm.");
98
- expect(instructions).toContain("### IDENTITY.md");
99
- expect(instructions).not.toContain("do not include");
100
- });
101
- });
@@ -1,149 +0,0 @@
1
- import type { KlawConfig } from "klaw/plugin-sdk/config-contracts";
2
- import { buildRealtimeVoiceAgentConsultPolicyInstructions } from "klaw/plugin-sdk/realtime-voice";
3
- import { root } from "klaw/plugin-sdk/security-runtime";
4
- import type { VoiceCallConfig } from "./config.js";
5
- import type { CoreAgentDeps, CoreConfig } from "./core-bridge.js";
6
-
7
- type AgentEntryLike = {
8
- id?: unknown;
9
- systemPromptOverride?: unknown;
10
- };
11
-
12
- type VoiceIdentityLike = {
13
- name?: unknown;
14
- emoji?: unknown;
15
- theme?: unknown;
16
- creature?: unknown;
17
- vibe?: unknown;
18
- };
19
-
20
- function normalizeString(value: unknown): string | undefined {
21
- return typeof value === "string" && value.trim() ? value.trim() : undefined;
22
- }
23
-
24
- function readAgentEntries(cfg: CoreConfig): AgentEntryLike[] {
25
- const agents = (cfg as { agents?: { list?: unknown } }).agents;
26
- return Array.isArray(agents?.list)
27
- ? agents.list.filter((entry): entry is AgentEntryLike =>
28
- Boolean(entry && typeof entry === "object"),
29
- )
30
- : [];
31
- }
32
-
33
- function resolveAgentSystemPromptOverride(cfg: CoreConfig, agentId: string): string | undefined {
34
- const entries = readAgentEntries(cfg);
35
- const entry = entries.find((candidate) => normalizeString(candidate.id) === agentId);
36
- return (
37
- normalizeString(entry?.systemPromptOverride) ??
38
- normalizeString(
39
- (cfg as { agents?: { defaults?: { systemPromptOverride?: unknown } } }).agents?.defaults
40
- ?.systemPromptOverride,
41
- )
42
- );
43
- }
44
-
45
- function limitText(text: string, maxChars: number): string {
46
- if (text.length <= maxChars) {
47
- return text;
48
- }
49
- return `${text.slice(0, Math.max(0, maxChars - 32)).trimEnd()}\n[truncated]`;
50
- }
51
-
52
- async function readWorkspaceVoiceContextFiles(params: {
53
- workspaceDir: string;
54
- files: readonly string[];
55
- maxChars: number;
56
- }): Promise<string[]> {
57
- const sections: string[] = [];
58
- let remaining = params.maxChars;
59
- const workspaceRoot = await root(params.workspaceDir).catch(() => null);
60
- if (!workspaceRoot) {
61
- return sections;
62
- }
63
- for (const file of params.files) {
64
- if (remaining <= 0) {
65
- continue;
66
- }
67
- const content = await workspaceRoot.readText(file).catch(() => undefined);
68
- const trimmed = content?.trim();
69
- if (!trimmed) {
70
- continue;
71
- }
72
- const body = limitText(trimmed, Math.max(0, remaining - file.length - 16));
73
- const section = `### ${file}\n${body}`;
74
- sections.push(section);
75
- remaining -= section.length;
76
- }
77
- return sections;
78
- }
79
-
80
- export async function buildRealtimeVoiceInstructions(params: {
81
- baseInstructions: string;
82
- config: VoiceCallConfig;
83
- coreConfig: CoreConfig;
84
- agentRuntime: CoreAgentDeps;
85
- }): Promise<string> {
86
- const { config } = params;
87
- const sections: string[] = [params.baseInstructions];
88
- const consultGuidance = buildRealtimeVoiceAgentConsultPolicyInstructions(config.realtime);
89
- if (consultGuidance) {
90
- sections.push(consultGuidance);
91
- }
92
-
93
- const contextConfig = config.realtime.agentContext;
94
- if (!contextConfig.enabled) {
95
- return sections.filter(Boolean).join("\n\n");
96
- }
97
-
98
- const agentId = config.agentId ?? "main";
99
- const capsule: string[] = [
100
- "Klaw agent voice context:",
101
- `- Agent id: ${agentId}`,
102
- "- Use this context to match the Klaw agent's personality and standing preferences on fast voice turns.",
103
- "- Treat this as compact context only; call klaw_agent_consult when the caller needs the full agent brain, tools, memory, or workspace state.",
104
- ];
105
-
106
- if (contextConfig.includeIdentity) {
107
- const identity = params.agentRuntime.resolveAgentIdentity(
108
- params.coreConfig as KlawConfig,
109
- agentId,
110
- ) as VoiceIdentityLike | undefined;
111
- const identityLines = [
112
- normalizeString(identity?.name) ? `- Name: ${normalizeString(identity?.name)}` : undefined,
113
- normalizeString(identity?.emoji) ? `- Emoji: ${normalizeString(identity?.emoji)}` : undefined,
114
- normalizeString(identity?.vibe) ? `- Vibe: ${normalizeString(identity?.vibe)}` : undefined,
115
- normalizeString(identity?.theme) ? `- Theme: ${normalizeString(identity?.theme)}` : undefined,
116
- normalizeString(identity?.creature)
117
- ? `- Creature/persona: ${normalizeString(identity?.creature)}`
118
- : undefined,
119
- ].filter(Boolean);
120
- if (identityLines.length > 0) {
121
- capsule.push(`Configured identity:\n${identityLines.join("\n")}`);
122
- }
123
- }
124
-
125
- if (contextConfig.includeSystemPrompt) {
126
- const systemPrompt = resolveAgentSystemPromptOverride(params.coreConfig, agentId);
127
- if (systemPrompt) {
128
- capsule.push(`Configured system prompt override:\n${systemPrompt}`);
129
- }
130
- }
131
-
132
- if (contextConfig.includeWorkspaceFiles) {
133
- const workspaceDir = params.agentRuntime.resolveAgentWorkspaceDir(
134
- params.coreConfig as KlawConfig,
135
- agentId,
136
- );
137
- const fileSections = await readWorkspaceVoiceContextFiles({
138
- workspaceDir,
139
- files: contextConfig.files,
140
- maxChars: contextConfig.maxChars,
141
- });
142
- if (fileSections.length > 0) {
143
- capsule.push(`Workspace voice context:\n${fileSections.join("\n\n")}`);
144
- }
145
- }
146
-
147
- sections.push(limitText(capsule.join("\n\n"), contextConfig.maxChars));
148
- return sections.filter(Boolean).join("\n\n");
149
- }
@@ -1,3 +0,0 @@
1
- import { REALTIME_VOICE_AGENT_CONSULT_TOOL_NAME } from "klaw/plugin-sdk/realtime-voice";
2
-
3
- export const DEFAULT_VOICE_CALL_REALTIME_INSTRUCTIONS = `You are Klaw's phone-call realtime voice interface. Keep spoken replies brief and natural. When a question needs deeper reasoning, current information, or tools, call ${REALTIME_VOICE_AGENT_CONSULT_TOOL_NAME} before answering.`;
@@ -1,74 +0,0 @@
1
- import type { KlawConfig } from "klaw/plugin-sdk/config-contracts";
2
- import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
3
- import type { VoiceCallRealtimeFastContextConfig } from "./config.js";
4
-
5
- const mocks = vi.hoisted(() => ({
6
- resolveRealtimeVoiceFastContextConsult: vi.fn(),
7
- }));
8
-
9
- vi.mock("klaw/plugin-sdk/realtime-voice", () => ({
10
- resolveRealtimeVoiceFastContextConsult: mocks.resolveRealtimeVoiceFastContextConsult,
11
- }));
12
-
13
- import { resolveRealtimeFastContextConsult } from "./realtime-fast-context.js";
14
-
15
- const cfg = {} as KlawConfig;
16
-
17
- function createFastContextConfig(
18
- overrides: Partial<VoiceCallRealtimeFastContextConfig> = {},
19
- ): VoiceCallRealtimeFastContextConfig {
20
- return {
21
- enabled: true,
22
- timeoutMs: 800,
23
- maxResults: 3,
24
- sources: ["memory", "sessions"],
25
- fallbackToConsult: false,
26
- ...overrides,
27
- };
28
- }
29
-
30
- function createLogger() {
31
- return {
32
- debug: vi.fn(),
33
- warn: vi.fn(),
34
- };
35
- }
36
-
37
- describe("resolveRealtimeFastContextConsult", () => {
38
- beforeEach(() => {
39
- mocks.resolveRealtimeVoiceFastContextConsult.mockReset();
40
- });
41
-
42
- afterEach(() => {
43
- vi.useRealTimers();
44
- });
45
-
46
- it("passes voice-call labels into the SDK fast context resolver", async () => {
47
- const logger = createLogger();
48
- mocks.resolveRealtimeVoiceFastContextConsult.mockResolvedValue({ handled: false });
49
-
50
- await expect(
51
- resolveRealtimeFastContextConsult({
52
- cfg,
53
- agentId: "main",
54
- sessionKey: "voice:15550001234",
55
- config: createFastContextConfig({ fallbackToConsult: true }),
56
- args: { question: "What do you remember?" },
57
- logger,
58
- }),
59
- ).resolves.toEqual({ handled: false });
60
-
61
- expect(mocks.resolveRealtimeVoiceFastContextConsult).toHaveBeenCalledWith({
62
- cfg,
63
- agentId: "main",
64
- sessionKey: "voice:15550001234",
65
- config: createFastContextConfig({ fallbackToConsult: true }),
66
- args: { question: "What do you remember?" },
67
- logger,
68
- labels: {
69
- audienceLabel: "caller",
70
- contextName: "Klaw memory or session context",
71
- },
72
- });
73
- });
74
- });
@@ -1,27 +0,0 @@
1
- import type { KlawConfig } from "klaw/plugin-sdk/config-contracts";
2
- import {
3
- resolveRealtimeVoiceFastContextConsult,
4
- type RealtimeVoiceFastContextConsultResult,
5
- type RealtimeVoiceFastContextConfig,
6
- } from "klaw/plugin-sdk/realtime-voice";
7
-
8
- type Logger = {
9
- debug?: (message: string) => void;
10
- };
11
-
12
- export async function resolveRealtimeFastContextConsult(params: {
13
- cfg: KlawConfig;
14
- agentId: string;
15
- sessionKey: string;
16
- config: RealtimeVoiceFastContextConfig;
17
- args: unknown;
18
- logger: Logger;
19
- }): Promise<RealtimeVoiceFastContextConsultResult> {
20
- return await resolveRealtimeVoiceFastContextConsult({
21
- ...params,
22
- labels: {
23
- audienceLabel: "caller",
24
- contextName: "Klaw memory or session context",
25
- },
26
- });
27
- }
@@ -1,4 +0,0 @@
1
- export {
2
- getRealtimeTranscriptionProvider,
3
- listRealtimeTranscriptionProviders,
4
- } from "klaw/plugin-sdk/realtime-transcription";
@@ -1,5 +0,0 @@
1
- export {
2
- getRealtimeVoiceProvider,
3
- listRealtimeVoiceProviders,
4
- resolveConfiguredRealtimeVoiceProvider,
5
- } from "klaw/plugin-sdk/realtime-voice";