@opencx/mcp 1.15.28 → 1.15.29

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.
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=follow-up-tools.spec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"follow-up-tools.spec.d.ts","sourceRoot":"","sources":["../src/follow-up-tools.spec.ts"],"names":[],"mappings":""}
@@ -0,0 +1,139 @@
1
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
2
+ import { connectInMemory, fetchCallUrl, toolTextResultSchema } from './spec-helpers.js';
3
+ import { jsonValueSchema } from './utils/json.js';
4
+ function mockResponse(body, status = 200, statusText = 'OK') {
5
+ return new Response(JSON.stringify(body), { status, statusText });
6
+ }
7
+ function recordedCall(fetchSpy) {
8
+ const [call] = fetchSpy.mock.calls;
9
+ if (!call)
10
+ throw new Error('fetch was not called');
11
+ return call;
12
+ }
13
+ function callMethod(call) {
14
+ const [input, init] = call;
15
+ if (input instanceof Request)
16
+ return input.method;
17
+ return init?.method ?? 'GET';
18
+ }
19
+ async function callBody(call) {
20
+ const [input, init] = call;
21
+ const text = input instanceof Request ? await input.clone().text() : init?.body;
22
+ return typeof text === 'string' && text ? jsonValueSchema.parse(JSON.parse(text)) : undefined;
23
+ }
24
+ const followUp = {
25
+ id: 'fu-1',
26
+ session_id: '11111111-1111-4111-8111-111111111111',
27
+ ticket_number: 42,
28
+ prompt: 'check the export',
29
+ fire_at: '2026-09-05T15:00:00.000Z',
30
+ is_active: true,
31
+ created_at: '2026-09-05T14:55:00.000Z',
32
+ };
33
+ describe('follow-up tools', () => {
34
+ let client;
35
+ let cleanup;
36
+ let fetchSpy;
37
+ beforeEach(async () => {
38
+ fetchSpy = vi.fn();
39
+ vi.stubGlobal('fetch', fetchSpy);
40
+ ({ client, cleanup } = await connectInMemory());
41
+ });
42
+ afterEach(async () => {
43
+ await cleanup();
44
+ vi.unstubAllGlobals();
45
+ });
46
+ it('registers all three tools with session_id as a required argument', async () => {
47
+ const { tools } = await client.listTools();
48
+ const names = tools.map((t) => t.name);
49
+ expect(names).toEqual(expect.arrayContaining(['schedule_followup', 'list_followups', 'cancel_followup']));
50
+ for (const name of ['schedule_followup', 'list_followups', 'cancel_followup']) {
51
+ const tool = tools.find((t) => t.name === name);
52
+ expect(tool?.inputSchema.required, name).toContain('session_id');
53
+ }
54
+ const schedule = tools.find((t) => t.name === 'schedule_followup');
55
+ expect(schedule?.description).toContain('continue THIS session later');
56
+ expect(schedule?.description).toContain('back off (5 → 15 → 60 minutes)');
57
+ });
58
+ it('schedule_followup POSTs to the session follow-ups route with the API body shape', async () => {
59
+ fetchSpy.mockResolvedValueOnce(mockResponse(followUp, 201, 'Created'));
60
+ const result = toolTextResultSchema.parse(await client.callTool({
61
+ name: 'schedule_followup',
62
+ arguments: { session_id: followUp.session_id, delay_minutes: 5, prompt: 'check the export' },
63
+ }));
64
+ const call = recordedCall(fetchSpy);
65
+ expect(fetchCallUrl(call)).toBe(`https://api.test.com/chat/sessions/${followUp.session_id}/follow-ups`);
66
+ expect(callMethod(call)).toBe('POST');
67
+ expect(await callBody(call)).toEqual({ delay_minutes: 5, prompt: 'check the export' });
68
+ expect(result.isError).toBeUndefined();
69
+ expect(JSON.parse(result.content[0].text)).toEqual(followUp);
70
+ });
71
+ it('schedule_followup rejects a delay outside 1–43200 or an empty prompt before calling the API', async () => {
72
+ for (const args of [
73
+ { session_id: 's', delay_minutes: 0, prompt: 'x' },
74
+ { session_id: 's', delay_minutes: 43201, prompt: 'x' },
75
+ { session_id: 's', delay_minutes: 2.5, prompt: 'x' },
76
+ { session_id: 's', delay_minutes: 5, prompt: '' },
77
+ { session_id: '', delay_minutes: 5, prompt: 'x' },
78
+ ]) {
79
+ const result = toolTextResultSchema.parse(await client.callTool({ name: 'schedule_followup', arguments: args }));
80
+ expect(result.isError, JSON.stringify(args)).toBe(true);
81
+ }
82
+ expect(fetchSpy).not.toHaveBeenCalled();
83
+ });
84
+ it('schedule_followup surfaces a cap refusal (409) as a tool error the model can act on', async () => {
85
+ fetchSpy.mockResolvedValueOnce(mockResponse({ message: 'At most 5 pending follow-ups per session' }, 409, 'Conflict'));
86
+ const result = toolTextResultSchema.parse(await client.callTool({
87
+ name: 'schedule_followup',
88
+ arguments: { session_id: 's', delay_minutes: 5, prompt: 'x' },
89
+ }));
90
+ expect(result.isError).toBe(true);
91
+ expect(result.content[0].text).toContain('cap reached');
92
+ expect(result.content[0].text).toContain('At most 5 pending');
93
+ });
94
+ it('schedule_followup tells the model not to retry when the feature is off (403)', async () => {
95
+ fetchSpy.mockResolvedValueOnce(mockResponse({ message: 'nope' }, 403, 'Forbidden'));
96
+ const result = toolTextResultSchema.parse(await client.callTool({
97
+ name: 'schedule_followup',
98
+ arguments: { session_id: 's', delay_minutes: 5, prompt: 'x' },
99
+ }));
100
+ expect(result.isError).toBe(true);
101
+ expect(result.content[0].text).toContain('not enabled');
102
+ expect(result.content[0].text).toContain('do not retry');
103
+ });
104
+ it('list_followups GETs the session follow-ups route', async () => {
105
+ fetchSpy.mockResolvedValueOnce(mockResponse({ follow_ups: [followUp], limits: { max_active_per_session: 5, max_total_per_session: 50 } }));
106
+ const result = toolTextResultSchema.parse(await client.callTool({ name: 'list_followups', arguments: { session_id: 'abc' } }));
107
+ const call = recordedCall(fetchSpy);
108
+ expect(fetchCallUrl(call)).toBe('https://api.test.com/chat/sessions/abc/follow-ups');
109
+ expect(callMethod(call)).toBe('GET');
110
+ expect(JSON.parse(result.content[0].text).follow_ups).toEqual([followUp]);
111
+ });
112
+ it('cancel_followup DELETEs the follow-up route and returns the inactive follow-up', async () => {
113
+ fetchSpy.mockResolvedValueOnce(mockResponse({ ...followUp, is_active: false }));
114
+ const result = toolTextResultSchema.parse(await client.callTool({
115
+ name: 'cancel_followup',
116
+ arguments: { session_id: 'abc', follow_up_id: 'fu-1' },
117
+ }));
118
+ const call = recordedCall(fetchSpy);
119
+ expect(fetchCallUrl(call)).toBe('https://api.test.com/chat/sessions/abc/follow-ups/fu-1');
120
+ expect(callMethod(call)).toBe('DELETE');
121
+ expect(JSON.parse(result.content[0].text).is_active).toBe(false);
122
+ });
123
+ it('a 404 (foreign or unknown session / follow-up) is a tool error, not a crash', async () => {
124
+ fetchSpy.mockResolvedValueOnce(mockResponse({ message: 'Not Found' }, 404, 'Not Found'));
125
+ const result = toolTextResultSchema.parse(await client.callTool({
126
+ name: 'cancel_followup',
127
+ arguments: { session_id: 'abc', follow_up_id: 'nope' },
128
+ }));
129
+ expect(result.isError).toBe(true);
130
+ expect(result.content[0].text).toContain('No such session or follow-up');
131
+ });
132
+ it('an unexpected API failure (500) still propagates as an error result', async () => {
133
+ fetchSpy.mockResolvedValueOnce(mockResponse({ message: 'boom' }, 500, 'Internal Server Error'));
134
+ const result = toolTextResultSchema.parse(await client.callTool({ name: 'list_followups', arguments: { session_id: 'abc' } }));
135
+ expect(result.isError).toBe(true);
136
+ expect(result.content[0].text).toContain('500');
137
+ });
138
+ });
139
+ //# sourceMappingURL=follow-up-tools.spec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"follow-up-tools.spec.js","sourceRoot":"","sources":["../src/follow-up-tools.spec.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=phone-agent-card-spec.spec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"phone-agent-card-spec.spec.d.ts","sourceRoot":"","sources":["../src/phone-agent-card-spec.spec.ts"],"names":[],"mappings":""}
@@ -0,0 +1,29 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { buildPhoneAgentCardSpec } from './tools/phone.js';
3
+ /**
4
+ * The card a phone tool hands back is a ` ```spec ` fence of JSONL patch
5
+ * lines — the one format the widget stream, the widget's history parser, and
6
+ * the inbox all render. A whole-tree object would be dropped by the stream.
7
+ */
8
+ describe('buildPhoneAgentCardSpec', () => {
9
+ it('emits root-first patch lines inside a spec fence', () => {
10
+ const fence = buildPhoneAgentCardSpec({ id: 'a1', name: 'Support line', model: 'oppie-vox-livekit' });
11
+ const lines = fence.split('\n');
12
+ expect(lines[0]).toBe('```spec');
13
+ expect(lines.at(-1)).toBe('```');
14
+ const patches = lines.slice(1, -1).map((line) => JSON.parse(line));
15
+ expect(patches).toEqual([
16
+ { op: 'add', path: '/root', value: 'phone-agent-a1' },
17
+ {
18
+ op: 'add',
19
+ path: '/elements/phone-agent-a1',
20
+ value: {
21
+ type: 'PhoneAgentCard',
22
+ props: { agentId: 'a1', agentName: 'Support line', model: 'oppie-vox-livekit' },
23
+ children: [],
24
+ },
25
+ },
26
+ ]);
27
+ });
28
+ });
29
+ //# sourceMappingURL=phone-agent-card-spec.spec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"phone-agent-card-spec.spec.js","sourceRoot":"","sources":["../src/phone-agent-card-spec.spec.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,uBAAuB,EAAE,MAAM,kBAAkB,CAAC;AAE3D;;;;GAIG;AACH,QAAQ,CAAC,yBAAyB,EAAE,GAAG,EAAE;IACvC,EAAE,CAAC,kDAAkD,EAAE,GAAG,EAAE;QAC1D,MAAM,KAAK,GAAG,uBAAuB,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,cAAc,EAAE,KAAK,EAAE,mBAAmB,EAAE,CAAC,CAAC;QACtG,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAChC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACjC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACjC,MAAM,OAAO,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;QACnE,MAAM,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC;YACtB,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,gBAAgB,EAAE;YACrD;gBACE,EAAE,EAAE,KAAK;gBACT,IAAI,EAAE,0BAA0B;gBAChC,KAAK,EAAE;oBACL,IAAI,EAAE,gBAAgB;oBACtB,KAAK,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,cAAc,EAAE,KAAK,EAAE,mBAAmB,EAAE;oBAC/E,QAAQ,EAAE,EAAE;iBACb;aACF;SACF,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
package/dist/schema.d.ts CHANGED
@@ -616,6 +616,50 @@ export interface paths {
616
616
  patch?: never;
617
617
  trace?: never;
618
618
  };
619
+ "/chat/sessions/{session_id}/follow-ups": {
620
+ parameters: {
621
+ query?: never;
622
+ header?: never;
623
+ path?: never;
624
+ cookie?: never;
625
+ };
626
+ /**
627
+ * List the AI follow-ups scheduled on a chat session
628
+ * @description Every follow-up scheduled on this session, pending ones first. A follow-up stays listed after it fires or is cancelled, with `is_active` false.
629
+ */
630
+ get: operations["listChatSessionFollowUps"];
631
+ put?: never;
632
+ /**
633
+ * Schedule an AI follow-up on a chat session
634
+ * @description Schedules the AI to continue this session later with the given prompt as guidance. At most 5 pending and 50 total follow-ups per session (409 when exceeded). Requires the scheduled follow-ups feature (403 otherwise).
635
+ */
636
+ post: operations["createChatSessionFollowUp"];
637
+ delete?: never;
638
+ options?: never;
639
+ head?: never;
640
+ patch?: never;
641
+ trace?: never;
642
+ };
643
+ "/chat/sessions/{session_id}/follow-ups/{follow_up_id}": {
644
+ parameters: {
645
+ query?: never;
646
+ header?: never;
647
+ path?: never;
648
+ cookie?: never;
649
+ };
650
+ get?: never;
651
+ put?: never;
652
+ post?: never;
653
+ /**
654
+ * Cancel a pending AI follow-up
655
+ * @description Cancels a pending follow-up so it never fires. Cancelling one that already fired or was cancelled is a no-op and returns it as is.
656
+ */
657
+ delete: operations["cancelChatSessionFollowUp"];
658
+ options?: never;
659
+ head?: never;
660
+ patch?: never;
661
+ trace?: never;
662
+ };
619
663
  "/companies": {
620
664
  parameters: {
621
665
  query?: never;
@@ -3282,7 +3326,10 @@ export interface paths {
3282
3326
  };
3283
3327
  get?: never;
3284
3328
  put?: never;
3285
- /** Launch an outbound sequence — arms candidates for sending */
3329
+ /**
3330
+ * Launch an outbound sequence — arms candidates for sending
3331
+ * @description If the launch preview carries a regulatory_notice (voice steps into regulated countries), show it to a person and pass regulatory_acknowledged: true so the confirmation is recorded with the notice text. Informational — never blocks.
3332
+ */
3286
3333
  post: operations["launchSequence"];
3287
3334
  delete?: never;
3288
3335
  options?: never;
@@ -6213,6 +6260,8 @@ export interface components {
6213
6260
  support_office_hours_id?: string | null;
6214
6261
  /** @enum {string} */
6215
6262
  support_assignment_strategy?: "least-busy" | "round-robin" | "unassigned";
6263
+ /** @description Seconds between automatic assignments to the same agent under the least-busy strategy: 0 turns pacing off, otherwise 15 to 3600. */
6264
+ auto_assign_interval_seconds?: number;
6216
6265
  };
6217
6266
  CreateMiniAppDto: {
6218
6267
  name: string;
@@ -6517,6 +6566,10 @@ export interface components {
6517
6566
  type?: "string" | "number" | "boolean" | "date" | "string[]" | "number[]";
6518
6567
  }[];
6519
6568
  };
6569
+ LaunchSequenceInputDto: {
6570
+ /** @description True when the user was shown the regulatory notice and confirmed it — recorded with the notice text as shown, purely as a record. Never required; the launch behaves identically without it. */
6571
+ regulatory_acknowledged?: boolean | null;
6572
+ };
6520
6573
  SetSequenceStepsDto: {
6521
6574
  /** @description The full step list, replacing whatever is stored. Same validation as create: step 0 must have delay_minutes 0. */
6522
6575
  steps: ({
@@ -7347,6 +7400,12 @@ export interface components {
7347
7400
  */
7348
7401
  limit: number;
7349
7402
  };
7403
+ CreateChatSessionFollowUpInput: {
7404
+ /** @description Minutes from now until the AI continues the session (1–43200, i.e. up to 30 days). The reply runs at the first whole minute at or after that moment. */
7405
+ delay_minutes: number;
7406
+ /** @description Guidance for the fired reply — what to check or say when the session is continued. Not shown to the contact. */
7407
+ prompt: string;
7408
+ };
7350
7409
  UpdatePublicKnowledgebaseItem: {
7351
7410
  /** @description Contact segment ids this item should be restricted to. Pass [] to make it available to all contacts. */
7352
7411
  restricted_to_segments?: string[];
@@ -7368,11 +7427,6 @@ export interface components {
7368
7427
  channel: "web" | "email" | "phone" | "whatsapp" | "slack" | "sms" | "instagram" | "messenger" | "api" | "web_voice";
7369
7428
  /** @description Set true to enable autopilot, false to disable */
7370
7429
  autopilot_enabled: boolean;
7371
- /**
7372
- * @description AI agent version to use for this channel
7373
- * @enum {string}
7374
- */
7375
- ai_agent_version?: "v1" | "v2";
7376
7430
  };
7377
7431
  UpdateProhibitedTopicsInputDto: {
7378
7432
  /** @description Full replacement list of prohibited topics. Each entry is either a name string or an object with a name and an optional description. A name-only entry keeps whatever description that topic already has. Empty array clears all topics. Duplicates and blank entries are removed. */
@@ -8303,7 +8357,7 @@ export interface components {
8303
8357
  /** @constant */
8304
8358
  type: "response_skipped";
8305
8359
  /** @enum {string} */
8306
- reason?: "empty_response" | "potential_answers_dead_end" | "silent_handoff" | "assist_human_handling_recommended" | "superseded_by_newer_run" | "flagged_as_spam";
8360
+ reason?: "empty_response" | "potential_answers_dead_end" | "silent_handoff" | "assist_human_handling_recommended" | "superseded_by_newer_run" | "interrupted" | "flagged_as_spam";
8307
8361
  reasoning?: ({
8308
8362
  /** @constant */
8309
8363
  type: "reasoning";
@@ -8447,6 +8501,10 @@ export interface components {
8447
8501
  id: string;
8448
8502
  name: string;
8449
8503
  }[];
8504
+ /** @default null */
8505
+ pacing_until: string | null;
8506
+ /** @default null */
8507
+ skip_reason: "pacing" | null;
8450
8508
  } | null;
8451
8509
  peers: {
8452
8510
  agent_id: number;
@@ -8464,6 +8522,10 @@ export interface components {
8464
8522
  id: string;
8465
8523
  name: string;
8466
8524
  }[];
8525
+ /** @default null */
8526
+ pacing_until: string | null;
8527
+ /** @default null */
8528
+ skip_reason: "pacing" | null;
8467
8529
  }[];
8468
8530
  /** @default [] */
8469
8531
  required_skills: {
@@ -8549,6 +8611,10 @@ export interface components {
8549
8611
  id: string;
8550
8612
  name: string;
8551
8613
  }[];
8614
+ /** @default null */
8615
+ pacing_until: string | null;
8616
+ /** @default null */
8617
+ skip_reason: "pacing" | null;
8552
8618
  } | null;
8553
8619
  peers: {
8554
8620
  agent_id: number;
@@ -8566,6 +8632,10 @@ export interface components {
8566
8632
  id: string;
8567
8633
  name: string;
8568
8634
  }[];
8635
+ /** @default null */
8636
+ pacing_until: string | null;
8637
+ /** @default null */
8638
+ skip_reason: "pacing" | null;
8569
8639
  }[];
8570
8640
  /** @default [] */
8571
8641
  required_skills: {
@@ -9137,6 +9207,7 @@ export interface components {
9137
9207
  support_office_hours_id: string | null;
9138
9208
  /** @enum {string} */
9139
9209
  support_assignment_strategy: "least-busy" | "round-robin" | "unassigned";
9210
+ auto_assign_interval_seconds: number;
9140
9211
  zendesk_group_id: string | null;
9141
9212
  zendesk_group_channels: components["schemas"]["SessionChannel"][] | null;
9142
9213
  created_at: string | null;
@@ -10275,6 +10346,49 @@ export interface components {
10275
10346
  nullable: boolean;
10276
10347
  }[];
10277
10348
  };
10349
+ SequenceRegulatoryNoticeDto: {
10350
+ /** @description ISO 3166-1 alpha-2, sorted. */
10351
+ countries: string[];
10352
+ /** @description The checklist the confirmation refers to — show it with the country rules. */
10353
+ points: string[];
10354
+ cards: {
10355
+ /** @description ISO 3166-1 alpha-2. */
10356
+ code: string;
10357
+ name: string;
10358
+ /** @enum {string} */
10359
+ consent: "opt_in" | "opt_out" | "opt_in_b2c_opt_out_b2b" | "unregulated" | "unknown";
10360
+ consent_note: string;
10361
+ hours: {
10362
+ weekdays: string;
10363
+ saturday: string;
10364
+ sunday: string;
10365
+ holidays: string;
10366
+ /** @enum {string} */
10367
+ clock: "called_party" | "national" | "unknown";
10368
+ /**
10369
+ * @description Say "required" only for statute/regulation; the rest is guidance.
10370
+ * @enum {string}
10371
+ */
10372
+ basis: "statute" | "regulation" | "code_of_conduct" | "regulator_guidance" | "partial" | "none" | "unknown";
10373
+ note: string;
10374
+ };
10375
+ frequency: string | null;
10376
+ dnc: string;
10377
+ ai_disclosure: string;
10378
+ recording: string;
10379
+ origination: string;
10380
+ /** @enum {string} */
10381
+ origination_kind: "foreign_origin_prohibited" | "local_number_required" | "dedicated_prefix_required" | "registered_series_required" | "own_listed_number_required" | "cli_must_be_presented" | "none_found";
10382
+ penalties: string;
10383
+ /** @description Automated marketing calls are a prohibited class here regardless of consent. */
10384
+ prohibited: boolean;
10385
+ links: {
10386
+ name: string;
10387
+ url: string;
10388
+ }[];
10389
+ last_reviewed: string;
10390
+ }[];
10391
+ };
10278
10392
  LaunchPreviewDto: {
10279
10393
  /** @description A FORECAST of how many would start sending, not a promise: consent and suppression are re-checked at claim time and fail closed. */
10280
10394
  will_send: number;
@@ -10303,6 +10417,8 @@ export interface components {
10303
10417
  language: string;
10304
10418
  status: string;
10305
10419
  }[];
10420
+ /** @description Informational: present when the campaign has voice steps reaching a catalogued country. Show it before launching and pass regulatory_acknowledged: true when the user confirms — the launch itself never refuses over it. Null: nothing to show. */
10421
+ regulatory_notice: components["schemas"]["SequenceRegulatoryNoticeDto"] | null;
10306
10422
  };
10307
10423
  SequenceSendersDto: {
10308
10424
  senders: {
@@ -11560,6 +11676,10 @@ export interface components {
11560
11676
  id: string;
11561
11677
  name: string;
11562
11678
  }[];
11679
+ /** @default null */
11680
+ pacing_until: string | null;
11681
+ /** @default null */
11682
+ skip_reason: "pacing" | null;
11563
11683
  } | null;
11564
11684
  peers: {
11565
11685
  agent_id: number;
@@ -11577,6 +11697,10 @@ export interface components {
11577
11697
  id: string;
11578
11698
  name: string;
11579
11699
  }[];
11700
+ /** @default null */
11701
+ pacing_until: string | null;
11702
+ /** @default null */
11703
+ skip_reason: "pacing" | null;
11580
11704
  }[];
11581
11705
  /** @default [] */
11582
11706
  required_skills: {
@@ -11913,6 +12037,28 @@ export interface components {
11913
12037
  };
11914
12038
  };
11915
12039
  };
12040
+ ChatSessionFollowUpOutput: {
12041
+ /** @description Follow-up id — stable across the follow-up’s lifetime */
12042
+ id: string;
12043
+ session_id: string;
12044
+ ticket_number: number;
12045
+ prompt: string;
12046
+ /**
12047
+ * Format: date-time
12048
+ * @description When the follow-up fires (UTC)
12049
+ */
12050
+ fire_at: string;
12051
+ is_active: boolean;
12052
+ /** Format: date-time */
12053
+ created_at: string;
12054
+ };
12055
+ ChatSessionFollowUpListOutput: {
12056
+ follow_ups: components["schemas"]["ChatSessionFollowUpOutput"][];
12057
+ limits: {
12058
+ max_active_per_session: number;
12059
+ max_total_per_session: number;
12060
+ };
12061
+ };
11916
12062
  ContactSegment: {
11917
12063
  id: string;
11918
12064
  name: string;
@@ -12159,11 +12305,6 @@ export interface components {
12159
12305
  channel: string;
12160
12306
  /** @description Whether the AI autopilot is enabled on this channel */
12161
12307
  autopilot_enabled: boolean;
12162
- /**
12163
- * @description AI agent version used on this channel
12164
- * @enum {string}
12165
- */
12166
- ai_agent_version: "v1" | "v2";
12167
12308
  }[];
12168
12309
  };
12169
12310
  ProhibitedTopicsResponseDto: {
@@ -14618,6 +14759,108 @@ export interface operations {
14618
14759
  };
14619
14760
  };
14620
14761
  };
14762
+ listChatSessionFollowUps: {
14763
+ parameters: {
14764
+ query?: never;
14765
+ header?: never;
14766
+ path: {
14767
+ /** @description The unique identifier of the chat session */
14768
+ session_id: string;
14769
+ };
14770
+ cookie?: never;
14771
+ };
14772
+ requestBody?: never;
14773
+ responses: {
14774
+ /** @description Default Response */
14775
+ 200: {
14776
+ headers: {
14777
+ [name: string]: unknown;
14778
+ };
14779
+ content: {
14780
+ "application/json": components["schemas"]["ChatSessionFollowUpListOutput"];
14781
+ };
14782
+ };
14783
+ /** @description Internal Server Error */
14784
+ 500: {
14785
+ headers: {
14786
+ [name: string]: unknown;
14787
+ };
14788
+ content: {
14789
+ "application/json": components["schemas"]["ErrorDto"];
14790
+ };
14791
+ };
14792
+ };
14793
+ };
14794
+ createChatSessionFollowUp: {
14795
+ parameters: {
14796
+ query?: never;
14797
+ header?: never;
14798
+ path: {
14799
+ /** @description The unique identifier of the chat session */
14800
+ session_id: string;
14801
+ };
14802
+ cookie?: never;
14803
+ };
14804
+ requestBody: {
14805
+ content: {
14806
+ "application/json": components["schemas"]["CreateChatSessionFollowUpInput"];
14807
+ };
14808
+ };
14809
+ responses: {
14810
+ /** @description Default Response */
14811
+ 201: {
14812
+ headers: {
14813
+ [name: string]: unknown;
14814
+ };
14815
+ content: {
14816
+ "application/json": components["schemas"]["ChatSessionFollowUpOutput"];
14817
+ };
14818
+ };
14819
+ /** @description Internal Server Error */
14820
+ 500: {
14821
+ headers: {
14822
+ [name: string]: unknown;
14823
+ };
14824
+ content: {
14825
+ "application/json": components["schemas"]["ErrorDto"];
14826
+ };
14827
+ };
14828
+ };
14829
+ };
14830
+ cancelChatSessionFollowUp: {
14831
+ parameters: {
14832
+ query?: never;
14833
+ header?: never;
14834
+ path: {
14835
+ /** @description The unique identifier of the chat session */
14836
+ session_id: string;
14837
+ /** @description The follow-up id returned when it was scheduled */
14838
+ follow_up_id: string;
14839
+ };
14840
+ cookie?: never;
14841
+ };
14842
+ requestBody?: never;
14843
+ responses: {
14844
+ /** @description Default Response */
14845
+ 200: {
14846
+ headers: {
14847
+ [name: string]: unknown;
14848
+ };
14849
+ content: {
14850
+ "application/json": components["schemas"]["ChatSessionFollowUpOutput"];
14851
+ };
14852
+ };
14853
+ /** @description Internal Server Error */
14854
+ 500: {
14855
+ headers: {
14856
+ [name: string]: unknown;
14857
+ };
14858
+ content: {
14859
+ "application/json": components["schemas"]["ErrorDto"];
14860
+ };
14861
+ };
14862
+ };
14863
+ };
14621
14864
  listCompanies: {
14622
14865
  parameters: {
14623
14866
  query?: {
@@ -20576,7 +20819,11 @@ export interface operations {
20576
20819
  };
20577
20820
  cookie?: never;
20578
20821
  };
20579
- requestBody?: never;
20822
+ requestBody: {
20823
+ content: {
20824
+ "application/json": components["schemas"]["LaunchSequenceInputDto"] | null;
20825
+ };
20826
+ };
20580
20827
  responses: {
20581
20828
  /** @description Default Response */
20582
20829
  201: {