@agenticmail/core 0.9.27 → 0.9.28

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.
package/dist/index.cjs CHANGED
@@ -7514,19 +7514,19 @@ var PHONE_MISSION_STATES = [
7514
7514
  "failed",
7515
7515
  "cancelled"
7516
7516
  ];
7517
- var PHONE_SERVER_MAX_CALL_DURATION_SECONDS = 3600;
7517
+ var PHONE_SERVER_MAX_CALL_DURATION_SECONDS = 7200;
7518
7518
  var PHONE_SERVER_MAX_COST_PER_MISSION = 5;
7519
7519
  var PHONE_SERVER_MAX_ATTEMPTS = 3;
7520
7520
  var PHONE_TASK_MAX_LENGTH = 2e3;
7521
- var PHONE_SERVER_MAX_EXTENSION_SECONDS_PER_REQUEST = 300;
7522
- var PHONE_SERVER_MAX_EXTENSION_REQUESTS_PER_CALL = 4;
7523
- var PHONE_SERVER_MAX_TOTAL_EXTENSION_SECONDS = 600;
7521
+ var PHONE_SERVER_MAX_EXTENSION_SECONDS_PER_REQUEST = 900;
7522
+ var PHONE_SERVER_MAX_EXTENSION_REQUESTS_PER_CALL = 8;
7523
+ var PHONE_SERVER_MAX_TOTAL_EXTENSION_SECONDS = 3600;
7524
7524
  var DEFAULT_EXTENSION_POLICY = {
7525
- maxSecondsPerRequest: 120,
7526
- // 2 minutes
7527
- maxRequestsPerCall: 2,
7528
- maxTotalExtensionSeconds: 300
7525
+ maxSecondsPerRequest: 300,
7529
7526
  // 5 minutes
7527
+ maxRequestsPerCall: 4,
7528
+ maxTotalExtensionSeconds: 1200
7529
+ // 20 minutes
7530
7530
  };
7531
7531
  var PHONE_SERVER_MAX_CALLBACK_CHAIN = 3;
7532
7532
  var DEFAULT_CALLBACK_POLICY = {
@@ -10745,6 +10745,20 @@ var SCHEDULE_CALLBACK_TOOL = {
10745
10745
  required: ["delay_seconds", "summary_for_next_call"]
10746
10746
  }
10747
10747
  };
10748
+ var END_CALL_TOOL = {
10749
+ type: "function",
10750
+ name: "end_call",
10751
+ description: `Hang up the call. You MUST call this AFTER you have said goodbye and the conversation is complete \u2014 saying "I'll hang up now" is not enough on its own, the call stays open until you actually call this tool. Use it when: the task is done and you have signed off, the caller has indicated the conversation is over, you have scheduled a callback and said goodbye, or the caller has stopped responding for an extended period. Once called the call drops immediately; you cannot un-hang-up.`,
10752
+ parameters: {
10753
+ type: "object",
10754
+ properties: {
10755
+ reason: {
10756
+ type: "string",
10757
+ description: 'One short line for the audit trail \u2014 e.g. "task complete", "caller said bye", "scheduled callback, signing off". Optional but recommended.'
10758
+ }
10759
+ }
10760
+ }
10761
+ };
10748
10762
  var REALTIME_TOOL_DEFINITIONS = {
10749
10763
  ask_operator: ASK_OPERATOR_TOOL,
10750
10764
  web_search: WEB_SEARCH_TOOL,
@@ -10755,7 +10769,8 @@ var REALTIME_TOOL_DEFINITIONS = {
10755
10769
  load_skill: LOAD_SKILL_TOOL,
10756
10770
  get_call_status: GET_CALL_STATUS_TOOL,
10757
10771
  extend_call_time: EXTEND_CALL_TIME_TOOL,
10758
- schedule_callback: SCHEDULE_CALLBACK_TOOL
10772
+ schedule_callback: SCHEDULE_CALLBACK_TOOL,
10773
+ end_call: END_CALL_TOOL
10759
10774
  };
10760
10775
  function buildRealtimeToolGuidance(tools) {
10761
10776
  if (tools.length === 0) return "";
@@ -10802,6 +10817,24 @@ A skill's rendered playbook is now part of your instructions for the rest of the
10802
10817
  "When in doubt \u2014 out of time, out of extensions, the caller is uncertain \u2014 preferred order is: wrap up gracefully \u2192 schedule_callback \u2192 sign off. Never go silent or invent excuses; the right move is always a clean handoff to a future call."
10803
10818
  );
10804
10819
  }
10820
+ if (names.has("end_call")) {
10821
+ lines.push(
10822
+ "# Hanging up \u2014 you must call end_call to actually drop the line",
10823
+ `SAYING "I'll hang up now" or "goodbye" does NOT drop the call \u2014 the line stays open until you call the end_call tool. This is the single most important habit on a real phone call: after you have said your goodbye sentence, IMMEDIATELY call end_call({ reason: "..." }). Do not wait for the caller to hang up first; do not assume the system will close the line for you. Call end_call yourself.`,
10824
+ "",
10825
+ "When to call end_call:",
10826
+ "- The task is complete and you have just said goodbye.",
10827
+ '- The caller said "thanks, bye" or otherwise signalled they are done.',
10828
+ `- You scheduled a callback and said "I'll call you back at <when>".`,
10829
+ "- The caller has gone silent for an extended stretch and is clearly not coming back.",
10830
+ "- The operator told you to hang up.",
10831
+ "",
10832
+ "Do NOT call end_call:",
10833
+ "- Mid-conversation when there is still pending business.",
10834
+ "- Because you ran into a tool error \u2014 handle it and keep the call going.",
10835
+ "- During a hold while a tool is running \u2014 let the tool finish."
10836
+ );
10837
+ }
10805
10838
  return lines.join("\n");
10806
10839
  }
10807
10840
  function toolErrorText(err) {
@@ -11681,8 +11714,10 @@ var RealtimeVoiceBridge = class {
11681
11714
  }
11682
11715
  let granted = Math.min(asked, pol.maxSecondsPerRequest, remainingBudgetSeconds);
11683
11716
  const elapsedSeconds = Math.floor((this.nowFn() - this.callStartedAtMs) / 1e3);
11684
- const maxAllowedFromStart = 3600;
11685
- const hardCeilingRoom = Math.max(0, maxAllowedFromStart - (elapsedSeconds + this.getTimeRemainingSeconds()));
11717
+ const hardCeilingRoom = Math.max(
11718
+ 0,
11719
+ PHONE_SERVER_MAX_CALL_DURATION_SECONDS - (elapsedSeconds + this.getTimeRemainingSeconds())
11720
+ );
11686
11721
  granted = Math.min(granted, hardCeilingRoom);
11687
11722
  if (granted <= 0) {
11688
11723
  return {
@@ -11921,6 +11956,31 @@ var RealtimeVoiceBridge = class {
11921
11956
  return "\u2026\n" + joined.slice(joined.length - MAX_CALLBACK_TRANSCRIPT_DIGEST_LENGTH + 2);
11922
11957
  }
11923
11958
  // ─── Teardown ─────────────────────────────────────────
11959
+ /**
11960
+ * v0.9.82 — agent-initiated hangup. Called when the `end_call` tool
11961
+ * fires. Logs a marker, then routes through {@link end} so the
11962
+ * carrier sees the bye frame and `onEnd` fires exactly once (the
11963
+ * same teardown path the human-hangup case takes). The "agent-
11964
+ * requested" reason flows through to the mission transcript so a
11965
+ * post-call audit can tell apart "agent hung up" from "human hung
11966
+ * up" from "time budget exceeded".
11967
+ *
11968
+ * Returns the structured result the tool handler echoes back to the
11969
+ * model — even though by the time the model receives it the line
11970
+ * will already be closed, keeping a consistent return shape lets the
11971
+ * executor JSON-stringify deterministically.
11972
+ */
11973
+ endByAgentRequest(reason) {
11974
+ if (this.ended) {
11975
+ return { ok: false, message: "Call has already ended." };
11976
+ }
11977
+ const trimmed = (reason ?? "").trim();
11978
+ this.emitTranscript("system", `Agent requested hangup. Reason: ${trimmed || "unspecified"}`, {
11979
+ endedByAgent: true
11980
+ });
11981
+ this.end("agent-requested");
11982
+ return { ok: true, message: "Call ended." };
11983
+ }
11924
11984
  /**
11925
11985
  * End the bridge. Idempotent — the first call wins, later calls are
11926
11986
  * no-ops. Sends the carrier's end-of-call frame (if it has one — 46elks
package/dist/index.d.cts CHANGED
@@ -2618,7 +2618,7 @@ type PhoneNumberRisk = 'invalid' | 'standard' | 'premium_or_special';
2618
2618
  * be MORE restrictive than the server, never less. A phone mission places
2619
2619
  * real, billed calls — these bounds are the financial blast-radius cap.
2620
2620
  */
2621
- declare const PHONE_SERVER_MAX_CALL_DURATION_SECONDS = 3600;
2621
+ declare const PHONE_SERVER_MAX_CALL_DURATION_SECONDS = 7200;
2622
2622
  declare const PHONE_SERVER_MAX_COST_PER_MISSION = 5;
2623
2623
  declare const PHONE_SERVER_MAX_ATTEMPTS = 3;
2624
2624
  /** Hard cap on the free-text `task` fed to the voice runtime. */
@@ -3692,6 +3692,24 @@ declare class RealtimeVoiceBridge {
3692
3692
  * Always honours {@link MAX_CALLBACK_TRANSCRIPT_DIGEST_LENGTH}.
3693
3693
  */
3694
3694
  private composeTranscriptDigest;
3695
+ /**
3696
+ * v0.9.82 — agent-initiated hangup. Called when the `end_call` tool
3697
+ * fires. Logs a marker, then routes through {@link end} so the
3698
+ * carrier sees the bye frame and `onEnd` fires exactly once (the
3699
+ * same teardown path the human-hangup case takes). The "agent-
3700
+ * requested" reason flows through to the mission transcript so a
3701
+ * post-call audit can tell apart "agent hung up" from "human hung
3702
+ * up" from "time budget exceeded".
3703
+ *
3704
+ * Returns the structured result the tool handler echoes back to the
3705
+ * model — even though by the time the model receives it the line
3706
+ * will already be closed, keeping a consistent return shape lets the
3707
+ * executor JSON-stringify deterministically.
3708
+ */
3709
+ endByAgentRequest(reason?: string): {
3710
+ ok: boolean;
3711
+ message: string;
3712
+ };
3695
3713
  /**
3696
3714
  * End the bridge. Idempotent — the first call wins, later calls are
3697
3715
  * no-ops. Sends the carrier's end-of-call frame (if it has one — 46elks
package/dist/index.d.ts CHANGED
@@ -2618,7 +2618,7 @@ type PhoneNumberRisk = 'invalid' | 'standard' | 'premium_or_special';
2618
2618
  * be MORE restrictive than the server, never less. A phone mission places
2619
2619
  * real, billed calls — these bounds are the financial blast-radius cap.
2620
2620
  */
2621
- declare const PHONE_SERVER_MAX_CALL_DURATION_SECONDS = 3600;
2621
+ declare const PHONE_SERVER_MAX_CALL_DURATION_SECONDS = 7200;
2622
2622
  declare const PHONE_SERVER_MAX_COST_PER_MISSION = 5;
2623
2623
  declare const PHONE_SERVER_MAX_ATTEMPTS = 3;
2624
2624
  /** Hard cap on the free-text `task` fed to the voice runtime. */
@@ -3692,6 +3692,24 @@ declare class RealtimeVoiceBridge {
3692
3692
  * Always honours {@link MAX_CALLBACK_TRANSCRIPT_DIGEST_LENGTH}.
3693
3693
  */
3694
3694
  private composeTranscriptDigest;
3695
+ /**
3696
+ * v0.9.82 — agent-initiated hangup. Called when the `end_call` tool
3697
+ * fires. Logs a marker, then routes through {@link end} so the
3698
+ * carrier sees the bye frame and `onEnd` fires exactly once (the
3699
+ * same teardown path the human-hangup case takes). The "agent-
3700
+ * requested" reason flows through to the mission transcript so a
3701
+ * post-call audit can tell apart "agent hung up" from "human hung
3702
+ * up" from "time budget exceeded".
3703
+ *
3704
+ * Returns the structured result the tool handler echoes back to the
3705
+ * model — even though by the time the model receives it the line
3706
+ * will already be closed, keeping a consistent return shape lets the
3707
+ * executor JSON-stringify deterministically.
3708
+ */
3709
+ endByAgentRequest(reason?: string): {
3710
+ ok: boolean;
3711
+ message: string;
3712
+ };
3695
3713
  /**
3696
3714
  * End the bridge. Idempotent — the first call wins, later calls are
3697
3715
  * no-ops. Sends the carrier's end-of-call frame (if it has one — 46elks
package/dist/index.js CHANGED
@@ -5865,19 +5865,19 @@ var PHONE_MISSION_STATES = [
5865
5865
  "failed",
5866
5866
  "cancelled"
5867
5867
  ];
5868
- var PHONE_SERVER_MAX_CALL_DURATION_SECONDS = 3600;
5868
+ var PHONE_SERVER_MAX_CALL_DURATION_SECONDS = 7200;
5869
5869
  var PHONE_SERVER_MAX_COST_PER_MISSION = 5;
5870
5870
  var PHONE_SERVER_MAX_ATTEMPTS = 3;
5871
5871
  var PHONE_TASK_MAX_LENGTH = 2e3;
5872
- var PHONE_SERVER_MAX_EXTENSION_SECONDS_PER_REQUEST = 300;
5873
- var PHONE_SERVER_MAX_EXTENSION_REQUESTS_PER_CALL = 4;
5874
- var PHONE_SERVER_MAX_TOTAL_EXTENSION_SECONDS = 600;
5872
+ var PHONE_SERVER_MAX_EXTENSION_SECONDS_PER_REQUEST = 900;
5873
+ var PHONE_SERVER_MAX_EXTENSION_REQUESTS_PER_CALL = 8;
5874
+ var PHONE_SERVER_MAX_TOTAL_EXTENSION_SECONDS = 3600;
5875
5875
  var DEFAULT_EXTENSION_POLICY = {
5876
- maxSecondsPerRequest: 120,
5877
- // 2 minutes
5878
- maxRequestsPerCall: 2,
5879
- maxTotalExtensionSeconds: 300
5876
+ maxSecondsPerRequest: 300,
5880
5877
  // 5 minutes
5878
+ maxRequestsPerCall: 4,
5879
+ maxTotalExtensionSeconds: 1200
5880
+ // 20 minutes
5881
5881
  };
5882
5882
  var PHONE_SERVER_MAX_CALLBACK_CHAIN = 3;
5883
5883
  var DEFAULT_CALLBACK_POLICY = {
@@ -9096,6 +9096,20 @@ var SCHEDULE_CALLBACK_TOOL = {
9096
9096
  required: ["delay_seconds", "summary_for_next_call"]
9097
9097
  }
9098
9098
  };
9099
+ var END_CALL_TOOL = {
9100
+ type: "function",
9101
+ name: "end_call",
9102
+ description: `Hang up the call. You MUST call this AFTER you have said goodbye and the conversation is complete \u2014 saying "I'll hang up now" is not enough on its own, the call stays open until you actually call this tool. Use it when: the task is done and you have signed off, the caller has indicated the conversation is over, you have scheduled a callback and said goodbye, or the caller has stopped responding for an extended period. Once called the call drops immediately; you cannot un-hang-up.`,
9103
+ parameters: {
9104
+ type: "object",
9105
+ properties: {
9106
+ reason: {
9107
+ type: "string",
9108
+ description: 'One short line for the audit trail \u2014 e.g. "task complete", "caller said bye", "scheduled callback, signing off". Optional but recommended.'
9109
+ }
9110
+ }
9111
+ }
9112
+ };
9099
9113
  var REALTIME_TOOL_DEFINITIONS = {
9100
9114
  ask_operator: ASK_OPERATOR_TOOL,
9101
9115
  web_search: WEB_SEARCH_TOOL,
@@ -9106,7 +9120,8 @@ var REALTIME_TOOL_DEFINITIONS = {
9106
9120
  load_skill: LOAD_SKILL_TOOL,
9107
9121
  get_call_status: GET_CALL_STATUS_TOOL,
9108
9122
  extend_call_time: EXTEND_CALL_TIME_TOOL,
9109
- schedule_callback: SCHEDULE_CALLBACK_TOOL
9123
+ schedule_callback: SCHEDULE_CALLBACK_TOOL,
9124
+ end_call: END_CALL_TOOL
9110
9125
  };
9111
9126
  function buildRealtimeToolGuidance(tools) {
9112
9127
  if (tools.length === 0) return "";
@@ -9153,6 +9168,24 @@ A skill's rendered playbook is now part of your instructions for the rest of the
9153
9168
  "When in doubt \u2014 out of time, out of extensions, the caller is uncertain \u2014 preferred order is: wrap up gracefully \u2192 schedule_callback \u2192 sign off. Never go silent or invent excuses; the right move is always a clean handoff to a future call."
9154
9169
  );
9155
9170
  }
9171
+ if (names.has("end_call")) {
9172
+ lines.push(
9173
+ "# Hanging up \u2014 you must call end_call to actually drop the line",
9174
+ `SAYING "I'll hang up now" or "goodbye" does NOT drop the call \u2014 the line stays open until you call the end_call tool. This is the single most important habit on a real phone call: after you have said your goodbye sentence, IMMEDIATELY call end_call({ reason: "..." }). Do not wait for the caller to hang up first; do not assume the system will close the line for you. Call end_call yourself.`,
9175
+ "",
9176
+ "When to call end_call:",
9177
+ "- The task is complete and you have just said goodbye.",
9178
+ '- The caller said "thanks, bye" or otherwise signalled they are done.',
9179
+ `- You scheduled a callback and said "I'll call you back at <when>".`,
9180
+ "- The caller has gone silent for an extended stretch and is clearly not coming back.",
9181
+ "- The operator told you to hang up.",
9182
+ "",
9183
+ "Do NOT call end_call:",
9184
+ "- Mid-conversation when there is still pending business.",
9185
+ "- Because you ran into a tool error \u2014 handle it and keep the call going.",
9186
+ "- During a hold while a tool is running \u2014 let the tool finish."
9187
+ );
9188
+ }
9156
9189
  return lines.join("\n");
9157
9190
  }
9158
9191
  function toolErrorText(err) {
@@ -10032,8 +10065,10 @@ var RealtimeVoiceBridge = class {
10032
10065
  }
10033
10066
  let granted = Math.min(asked, pol.maxSecondsPerRequest, remainingBudgetSeconds);
10034
10067
  const elapsedSeconds = Math.floor((this.nowFn() - this.callStartedAtMs) / 1e3);
10035
- const maxAllowedFromStart = 3600;
10036
- const hardCeilingRoom = Math.max(0, maxAllowedFromStart - (elapsedSeconds + this.getTimeRemainingSeconds()));
10068
+ const hardCeilingRoom = Math.max(
10069
+ 0,
10070
+ PHONE_SERVER_MAX_CALL_DURATION_SECONDS - (elapsedSeconds + this.getTimeRemainingSeconds())
10071
+ );
10037
10072
  granted = Math.min(granted, hardCeilingRoom);
10038
10073
  if (granted <= 0) {
10039
10074
  return {
@@ -10272,6 +10307,31 @@ var RealtimeVoiceBridge = class {
10272
10307
  return "\u2026\n" + joined.slice(joined.length - MAX_CALLBACK_TRANSCRIPT_DIGEST_LENGTH + 2);
10273
10308
  }
10274
10309
  // ─── Teardown ─────────────────────────────────────────
10310
+ /**
10311
+ * v0.9.82 — agent-initiated hangup. Called when the `end_call` tool
10312
+ * fires. Logs a marker, then routes through {@link end} so the
10313
+ * carrier sees the bye frame and `onEnd` fires exactly once (the
10314
+ * same teardown path the human-hangup case takes). The "agent-
10315
+ * requested" reason flows through to the mission transcript so a
10316
+ * post-call audit can tell apart "agent hung up" from "human hung
10317
+ * up" from "time budget exceeded".
10318
+ *
10319
+ * Returns the structured result the tool handler echoes back to the
10320
+ * model — even though by the time the model receives it the line
10321
+ * will already be closed, keeping a consistent return shape lets the
10322
+ * executor JSON-stringify deterministically.
10323
+ */
10324
+ endByAgentRequest(reason) {
10325
+ if (this.ended) {
10326
+ return { ok: false, message: "Call has already ended." };
10327
+ }
10328
+ const trimmed = (reason ?? "").trim();
10329
+ this.emitTranscript("system", `Agent requested hangup. Reason: ${trimmed || "unspecified"}`, {
10330
+ endedByAgent: true
10331
+ });
10332
+ this.end("agent-requested");
10333
+ return { ok: true, message: "Call ended." };
10334
+ }
10275
10335
  /**
10276
10336
  * End the bridge. Idempotent — the first call wins, later calls are
10277
10337
  * no-ops. Sends the carrier's end-of-call frame (if it has one — 46elks
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agenticmail/core",
3
- "version": "0.9.27",
3
+ "version": "0.9.28",
4
4
  "description": "Core SDK for AgenticMail — email, SMS, and phone call-control for AI agents",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",