@wiro-ai/wiro-mcp 1.4.1 → 1.5.1

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 (226) hide show
  1. package/README.md +38 -15
  2. package/dist/client.d.ts +19 -6
  3. package/dist/client.d.ts.map +1 -1
  4. package/dist/client.js +44 -27
  5. package/dist/client.js.map +1 -1
  6. package/dist/instructions.d.ts.map +1 -1
  7. package/dist/instructions.js +7 -4
  8. package/dist/instructions.js.map +1 -1
  9. package/dist/playbooks.d.ts.map +1 -1
  10. package/dist/playbooks.js +46 -35
  11. package/dist/playbooks.js.map +1 -1
  12. package/dist/prompts.d.ts.map +1 -1
  13. package/dist/prompts.js +3 -2
  14. package/dist/prompts.js.map +1 -1
  15. package/dist/tools/apply-agent-skills.d.ts.map +1 -1
  16. package/dist/tools/apply-agent-skills.js +458 -109
  17. package/dist/tools/apply-agent-skills.js.map +1 -1
  18. package/dist/tools/bind-agent-project.d.ts +4 -0
  19. package/dist/tools/bind-agent-project.d.ts.map +1 -0
  20. package/dist/tools/bind-agent-project.js +405 -0
  21. package/dist/tools/bind-agent-project.js.map +1 -0
  22. package/dist/tools/cancel-agent-message.d.ts.map +1 -1
  23. package/dist/tools/cancel-agent-message.js +6 -3
  24. package/dist/tools/cancel-agent-message.js.map +1 -1
  25. package/dist/tools/cancel-task.js +3 -3
  26. package/dist/tools/cancel-task.js.map +1 -1
  27. package/dist/tools/clone-workflow.d.ts.map +1 -1
  28. package/dist/tools/clone-workflow.js +5 -4
  29. package/dist/tools/clone-workflow.js.map +1 -1
  30. package/dist/tools/connect-agent-oauth.d.ts.map +1 -1
  31. package/dist/tools/connect-agent-oauth.js +161 -40
  32. package/dist/tools/connect-agent-oauth.js.map +1 -1
  33. package/dist/tools/control-agent.d.ts.map +1 -1
  34. package/dist/tools/control-agent.js +109 -36
  35. package/dist/tools/control-agent.js.map +1 -1
  36. package/dist/tools/create-workflow.d.ts +3 -0
  37. package/dist/tools/create-workflow.d.ts.map +1 -1
  38. package/dist/tools/create-workflow.js +12 -11
  39. package/dist/tools/create-workflow.js.map +1 -1
  40. package/dist/tools/delete-agent-session.d.ts.map +1 -1
  41. package/dist/tools/delete-agent-session.js +18 -12
  42. package/dist/tools/delete-agent-session.js.map +1 -1
  43. package/dist/tools/delete-agent.d.ts.map +1 -1
  44. package/dist/tools/delete-agent.js +12 -9
  45. package/dist/tools/delete-agent.js.map +1 -1
  46. package/dist/tools/delete-workflow.d.ts.map +1 -1
  47. package/dist/tools/delete-workflow.js +5 -4
  48. package/dist/tools/delete-workflow.js.map +1 -1
  49. package/dist/tools/deploy-agent.d.ts.map +1 -1
  50. package/dist/tools/deploy-agent.js +239 -46
  51. package/dist/tools/deploy-agent.js.map +1 -1
  52. package/dist/tools/disconnect-agent-oauth.d.ts.map +1 -1
  53. package/dist/tools/disconnect-agent-oauth.js +32 -12
  54. package/dist/tools/disconnect-agent-oauth.js.map +1 -1
  55. package/dist/tools/get-account.d.ts.map +1 -1
  56. package/dist/tools/get-account.js +148 -42
  57. package/dist/tools/get-account.js.map +1 -1
  58. package/dist/tools/get-agent-activity.d.ts.map +1 -1
  59. package/dist/tools/get-agent-activity.js +185 -35
  60. package/dist/tools/get-agent-activity.js.map +1 -1
  61. package/dist/tools/get-agent-conversation.d.ts.map +1 -1
  62. package/dist/tools/get-agent-conversation.js +57 -35
  63. package/dist/tools/get-agent-conversation.js.map +1 -1
  64. package/dist/tools/get-agent-message.d.ts.map +1 -1
  65. package/dist/tools/get-agent-message.js +6 -4
  66. package/dist/tools/get-agent-message.js.map +1 -1
  67. package/dist/tools/get-agent-template.d.ts.map +1 -1
  68. package/dist/tools/get-agent-template.js +13 -17
  69. package/dist/tools/get-agent-template.js.map +1 -1
  70. package/dist/tools/get-model-schema.js +3 -3
  71. package/dist/tools/get-model-schema.js.map +1 -1
  72. package/dist/tools/get-my-agent.d.ts.map +1 -1
  73. package/dist/tools/get-my-agent.js +13 -8
  74. package/dist/tools/get-my-agent.js.map +1 -1
  75. package/dist/tools/get-playbook.d.ts.map +1 -1
  76. package/dist/tools/get-playbook.js +3 -2
  77. package/dist/tools/get-playbook.js.map +1 -1
  78. package/dist/tools/get-task-price.js +3 -3
  79. package/dist/tools/get-task-price.js.map +1 -1
  80. package/dist/tools/get-task.js +3 -3
  81. package/dist/tools/get-task.js.map +1 -1
  82. package/dist/tools/get-workflow.d.ts.map +1 -1
  83. package/dist/tools/get-workflow.js +38 -19
  84. package/dist/tools/get-workflow.js.map +1 -1
  85. package/dist/tools/index.d.ts +3 -1
  86. package/dist/tools/index.d.ts.map +1 -1
  87. package/dist/tools/index.js +6 -1
  88. package/dist/tools/index.js.map +1 -1
  89. package/dist/tools/kill-task.js +3 -3
  90. package/dist/tools/kill-task.js.map +1 -1
  91. package/dist/tools/list-agent-skills.d.ts.map +1 -1
  92. package/dist/tools/list-agent-skills.js +11 -8
  93. package/dist/tools/list-agent-skills.js.map +1 -1
  94. package/dist/tools/list-agent-templates.js +4 -4
  95. package/dist/tools/list-agent-templates.js.map +1 -1
  96. package/dist/tools/list-my-agents.d.ts.map +1 -1
  97. package/dist/tools/list-my-agents.js +11 -8
  98. package/dist/tools/list-my-agents.js.map +1 -1
  99. package/dist/tools/list-projects.d.ts +4 -0
  100. package/dist/tools/list-projects.d.ts.map +1 -0
  101. package/dist/tools/list-projects.js +141 -0
  102. package/dist/tools/list-projects.js.map +1 -0
  103. package/dist/tools/list-studio-results.js +3 -3
  104. package/dist/tools/list-studio-results.js.map +1 -1
  105. package/dist/tools/list-tasks.js +3 -3
  106. package/dist/tools/list-tasks.js.map +1 -1
  107. package/dist/tools/list-workflow-runs.d.ts.map +1 -1
  108. package/dist/tools/list-workflow-runs.js +6 -4
  109. package/dist/tools/list-workflow-runs.js.map +1 -1
  110. package/dist/tools/list-workflows.d.ts.map +1 -1
  111. package/dist/tools/list-workflows.js +7 -6
  112. package/dist/tools/list-workflows.js.map +1 -1
  113. package/dist/tools/manage-agent-billing.d.ts.map +1 -1
  114. package/dist/tools/manage-agent-billing.js +207 -59
  115. package/dist/tools/manage-agent-billing.js.map +1 -1
  116. package/dist/tools/manage-agent-custom-skill.d.ts.map +1 -1
  117. package/dist/tools/manage-agent-custom-skill.js +119 -15
  118. package/dist/tools/manage-agent-custom-skill.js.map +1 -1
  119. package/dist/tools/recommend-model.js +3 -3
  120. package/dist/tools/recommend-model.js.map +1 -1
  121. package/dist/tools/resume-workflow-run.d.ts.map +1 -1
  122. package/dist/tools/resume-workflow-run.js +3 -2
  123. package/dist/tools/resume-workflow-run.js.map +1 -1
  124. package/dist/tools/run-model.js +3 -3
  125. package/dist/tools/run-model.js.map +1 -1
  126. package/dist/tools/run-workflow.d.ts.map +1 -1
  127. package/dist/tools/run-workflow.js +3 -2
  128. package/dist/tools/run-workflow.js.map +1 -1
  129. package/dist/tools/save-workflow-from-agent.d.ts.map +1 -1
  130. package/dist/tools/save-workflow-from-agent.js +17 -12
  131. package/dist/tools/save-workflow-from-agent.js.map +1 -1
  132. package/dist/tools/schemas.d.ts +32 -26
  133. package/dist/tools/schemas.d.ts.map +1 -1
  134. package/dist/tools/schemas.js +67 -0
  135. package/dist/tools/schemas.js.map +1 -1
  136. package/dist/tools/search-docs.js +3 -3
  137. package/dist/tools/search-docs.js.map +1 -1
  138. package/dist/tools/search-models.js +3 -3
  139. package/dist/tools/search-models.js.map +1 -1
  140. package/dist/tools/send-agent-message.d.ts.map +1 -1
  141. package/dist/tools/send-agent-message.js +30 -9
  142. package/dist/tools/send-agent-message.js.map +1 -1
  143. package/dist/tools/set-agent-credentials.d.ts.map +1 -1
  144. package/dist/tools/set-agent-credentials.js +55 -61
  145. package/dist/tools/set-agent-credentials.js.map +1 -1
  146. package/dist/tools/update-agent.d.ts.map +1 -1
  147. package/dist/tools/update-agent.js +59 -20
  148. package/dist/tools/update-agent.js.map +1 -1
  149. package/dist/tools/update-workflow.d.ts +199 -0
  150. package/dist/tools/update-workflow.d.ts.map +1 -1
  151. package/dist/tools/update-workflow.js +20 -7
  152. package/dist/tools/update-workflow.js.map +1 -1
  153. package/dist/tools/upload-file.js +3 -3
  154. package/dist/tools/upload-file.js.map +1 -1
  155. package/dist/tools/wait-for-task.js +3 -3
  156. package/dist/tools/wait-for-task.js.map +1 -1
  157. package/dist/tools/wait-for-workflow-run.d.ts +3 -3
  158. package/dist/tools/wait-for-workflow-run.d.ts.map +1 -1
  159. package/dist/tools/wait-for-workflow-run.js +7 -6
  160. package/dist/tools/wait-for-workflow-run.js.map +1 -1
  161. package/dist/utils/agent-catalog.d.ts +59 -0
  162. package/dist/utils/agent-catalog.d.ts.map +1 -1
  163. package/dist/utils/agent-catalog.js +28 -7
  164. package/dist/utils/agent-catalog.js.map +1 -1
  165. package/dist/utils/agent-config.d.ts +172 -9
  166. package/dist/utils/agent-config.d.ts.map +1 -1
  167. package/dist/utils/agent-config.js +416 -60
  168. package/dist/utils/agent-config.js.map +1 -1
  169. package/dist/utils/agent-message.d.ts +36 -14
  170. package/dist/utils/agent-message.d.ts.map +1 -1
  171. package/dist/utils/agent-message.js +92 -26
  172. package/dist/utils/agent-message.js.map +1 -1
  173. package/dist/utils/agent-targets.d.ts +7 -3
  174. package/dist/utils/agent-targets.d.ts.map +1 -1
  175. package/dist/utils/agent-targets.js +35 -23
  176. package/dist/utils/agent-targets.js.map +1 -1
  177. package/dist/utils/agent-turns.d.ts +19 -12
  178. package/dist/utils/agent-turns.d.ts.map +1 -1
  179. package/dist/utils/agent-turns.js +97 -15
  180. package/dist/utils/agent-turns.js.map +1 -1
  181. package/dist/utils/api.d.ts +6 -0
  182. package/dist/utils/api.d.ts.map +1 -1
  183. package/dist/utils/api.js +22 -1
  184. package/dist/utils/api.js.map +1 -1
  185. package/dist/utils/format.d.ts.map +1 -1
  186. package/dist/utils/format.js +7 -13
  187. package/dist/utils/format.js.map +1 -1
  188. package/dist/utils/json-schema-dialect.d.ts.map +1 -1
  189. package/dist/utils/json-schema-dialect.js +4 -0
  190. package/dist/utils/json-schema-dialect.js.map +1 -1
  191. package/dist/utils/media.d.ts +8 -0
  192. package/dist/utils/media.d.ts.map +1 -1
  193. package/dist/utils/media.js +29 -0
  194. package/dist/utils/media.js.map +1 -1
  195. package/dist/utils/poll.d.ts +5 -2
  196. package/dist/utils/poll.d.ts.map +1 -1
  197. package/dist/utils/poll.js +10 -9
  198. package/dist/utils/poll.js.map +1 -1
  199. package/dist/utils/quoted-text.d.ts +28 -0
  200. package/dist/utils/quoted-text.d.ts.map +1 -0
  201. package/dist/utils/quoted-text.js +64 -0
  202. package/dist/utils/quoted-text.js.map +1 -0
  203. package/dist/utils/studio-runs.d.ts.map +1 -1
  204. package/dist/utils/studio-runs.js +7 -4
  205. package/dist/utils/studio-runs.js.map +1 -1
  206. package/dist/utils/user-agent.d.ts +197 -29
  207. package/dist/utils/user-agent.d.ts.map +1 -1
  208. package/dist/utils/user-agent.js +314 -48
  209. package/dist/utils/user-agent.js.map +1 -1
  210. package/dist/utils/wiro-project.d.ts +45 -0
  211. package/dist/utils/wiro-project.d.ts.map +1 -0
  212. package/dist/utils/wiro-project.js +66 -0
  213. package/dist/utils/wiro-project.js.map +1 -0
  214. package/dist/utils/workflow-save.d.ts +22 -1
  215. package/dist/utils/workflow-save.d.ts.map +1 -1
  216. package/dist/utils/workflow-save.js +122 -24
  217. package/dist/utils/workflow-save.js.map +1 -1
  218. package/dist/utils/workflow.d.ts +26 -13
  219. package/dist/utils/workflow.d.ts.map +1 -1
  220. package/dist/utils/workflow.js +51 -20
  221. package/dist/utils/workflow.js.map +1 -1
  222. package/dist/utils/workspace.d.ts +30 -4
  223. package/dist/utils/workspace.d.ts.map +1 -1
  224. package/dist/utils/workspace.js +95 -12
  225. package/dist/utils/workspace.js.map +1 -1
  226. package/package.json +1 -1
@@ -1,11 +1,11 @@
1
1
  import { randomUUID } from 'node:crypto';
2
2
  import { z } from 'zod';
3
3
  import { ApiResultError, asString, errorResult, errorText, isPlainObject, requireApiSuccess, toNumber, unixToIso, } from '../utils/api.js';
4
- import { workspaceHint } from '../utils/agent-config.js';
4
+ import { daysLeftText, readPlanChange, underOneDayLeft, workspaceHint } from '../utils/agent-config.js';
5
5
  import { DEFAULT_WAIT_SECONDS } from '../utils/poll.js';
6
6
  import { isPlainTeamMember, teamAgentBillingRefused } from '../utils/workspace.js';
7
- import { AMOUNT_TOLERANCE_USD, agentDashboardUrl, agentSummarySchema, asTier, chargeRoute, formatAgentSummaryLines, formatCredits, formatUsd, outcomeUnknown, readAgentDetail, roundUsd, sameAmount, toAgentDetail, toAgentSummary, } from '../utils/user-agent.js';
8
- import { nextActionSchema } from './schemas.js';
7
+ import { AMOUNT_TOLERANCE_USD, WALLET_NOT_READ_NOTE, agentDashboardUrl, agentSummarySchema, asTier, chargeRoute, formatAgentSummaryLines, formatCredits, formatUsd, isTransitionalStatus, isWalletNotRead, outcomeUnknown, readAgentDetail, roundUsd, sameAmount, toAgentDetail, toAgentSummary, } from '../utils/user-agent.js';
8
+ import { nextActionSchema, strictInput } from './schemas.js';
9
9
  const ACTIONS = ['subscribe', 'upgrade_to_pro', 'cancel', 'renew', 'buy_credits'];
10
10
  const quoteSchema = z.object({
11
11
  chargeNowUsd: z.number().nullable(),
@@ -13,6 +13,8 @@ const quoteSchema = z.object({
13
13
  maxChargeUsd: z.number().optional(),
14
14
  recurringUsd: z.number().nullable().optional(),
15
15
  monthlyCredits: z.number().nullable().optional(),
16
+ creditsNow: z.number().optional()
17
+ .describe('upgrade_to_pro: the credits this period gains now, below 0 when it loses them. monthlyCredits is what every renewal gives.'),
16
18
  creditsAdded: z.number().optional(),
17
19
  paidFrom: z.enum(['personal_wallet', 'team_wallet', 'card']),
18
20
  tier: z.enum(['starter', 'pro']).optional(),
@@ -22,6 +24,8 @@ const resultSchema = z.object({
22
24
  chargedUsd: z.number().nullable().optional(),
23
25
  monthlyPriceUsd: z.number().optional(),
24
26
  monthlyCredits: z.number().optional(),
27
+ creditsNow: z.number().optional()
28
+ .describe('upgrade_to_pro: the credits this period gained with it, below 0 when it lost them. monthlyCredits is what every renewal gives.'),
25
29
  creditsAdded: z.number().optional(),
26
30
  tier: z.string().optional(),
27
31
  previousTier: z.string().optional(),
@@ -46,20 +50,114 @@ function waitAction(guid, reason) {
46
50
  reason,
47
51
  };
48
52
  }
53
+ /**
54
+ * The wait after an upgrade, a pack or a renewal, which keep a running agent up:
55
+ * only an agent on its way, one that was stopping (it starts again) or one queued
56
+ * or starting, is worth waiting for.
57
+ */
58
+ function waitIfStarting(agent) {
59
+ return isTransitionalStatus(agent.status)
60
+ ? waitAction(agent.guid, 'Wait until the agent is running.')
61
+ : undefined;
62
+ }
63
+ /** After a renewal, Wiro queues a stopped or failed agent; a running one keeps running. */
64
+ function renewWait(agent) {
65
+ if (agent.status === 0 || agent.status === 5)
66
+ return waitAction(agent.guid, 'Wait until the agent is running again.');
67
+ return waitIfStarting(agent);
68
+ }
69
+ /**
70
+ * A new plan queues a stopped or failed agent, and one waiting for setup; a
71
+ * running one keeps running, and one that was stopping starts again. An agent
72
+ * without a plan runs only while extra credits last.
73
+ */
74
+ function subscribeStarts(status) {
75
+ return status !== 4;
76
+ }
77
+ function subscribeQuoteNote(status) {
78
+ switch (status) {
79
+ case 4:
80
+ return 'The agent keeps running: a new plan does not restart it.';
81
+ case 3:
82
+ return 'The agent is starting, and a new plan does not restart it.';
83
+ case 1:
84
+ return 'The agent is stopping, and it starts again right after the payment.';
85
+ case 2:
86
+ return 'The agent is already queued to start.';
87
+ default:
88
+ return 'The agent is queued to start right after the payment.';
89
+ }
90
+ }
91
+ function subscribeWait(agent) {
92
+ return subscribeStarts(agent.status) ? waitAction(agent.guid, 'Wait until the agent is running.') : undefined;
93
+ }
94
+ /** What an upgrade does to this period's credits. An allowance above Pro's is cut to Pro's for the time left. */
95
+ function upgradeCreditsText(creditsNow, renewal, done) {
96
+ if (creditsNow > 0) {
97
+ return `${formatCredits(creditsNow)} credits ${done ? 'came with it' : 'come now'} for the rest of this period${renewal}. `;
98
+ }
99
+ if (creditsNow < 0)
100
+ return `This period ${done ? 'lost' : 'loses'} ${formatCredits(-creditsNow)} credits for the time left${renewal}. `;
101
+ return `This period ${done ? 'kept' : 'keeps'} the credits it ${done ? 'had' : 'has'}${renewal}. `;
102
+ }
103
+ /** What a change to the plan does to the agent: a running one keeps running, and one that was stopping starts again. */
104
+ function planChangeAgentNote(status, what, quote = false) {
105
+ switch (status) {
106
+ case 4:
107
+ return `The agent keeps running: ${what} does not restart it.`;
108
+ case 3:
109
+ return `The agent is starting, and ${what} does not restart it.`;
110
+ case 1:
111
+ return quote
112
+ ? 'The agent is stopping; if it is still stopping when the payment goes through, it starts again.'
113
+ : 'The agent was stopping, and it starts again.';
114
+ case 2:
115
+ return 'The agent is queued to start.';
116
+ case 6:
117
+ return 'The agent still needs setup; the credits are there once it starts.';
118
+ case 0:
119
+ return 'The agent is stopped; the credits are there when it starts.';
120
+ case 5:
121
+ return 'The agent stopped with an error; the credits are there when it starts.';
122
+ default:
123
+ return 'The credits are there when the agent starts.';
124
+ }
125
+ }
126
+ /**
127
+ * The Pro price of this agent and what moving to it does today, as Wiro works it
128
+ * out on the plan the agent has now. null when it cannot be read: the quote then
129
+ * falls back to the Pro price the agent shows and to the most the upgrade can charge.
130
+ */
131
+ async function readProPreview(client, guid, signal) {
132
+ try {
133
+ const body = requireApiSuccess(await client.post('/UserAgent/PricingPreview', { useragentguid: guid, tier: 'pro' }, signal), 'The Pro price could not be read.');
134
+ const priceUsd = toNumber(body.totalPriceUsd);
135
+ const monthlyCredits = toNumber(body.totalMonthlyCredits);
136
+ return {
137
+ price: priceUsd !== null && monthlyCredits !== null ? { priceUsd, monthlyCredits } : null,
138
+ change: readPlanChange(body.planChange),
139
+ };
140
+ }
141
+ catch (error) {
142
+ if (signal.aborted)
143
+ throw error;
144
+ return null;
145
+ }
146
+ }
49
147
  function tierName(tier) {
50
148
  return tier === 'pro' ? 'Pro' : 'Starter';
51
149
  }
52
- /** The amount a confirmed call charges now, or null when it charges nothing. */
150
+ /** The amount a confirmed call charges now, or at most when that is worked out only then; null when it charges nothing. */
53
151
  function chargeOf(quote) {
54
- const amount = quote?.chargeNowUsd ?? null;
152
+ const amount = quote?.chargeNowUsd ?? quote?.maxChargeUsd ?? null;
55
153
  return amount !== null && amount > 0 ? amount : null;
56
154
  }
57
155
  /**
58
156
  * The amount the user approves with confirm_charge_usd, or null when the
59
- * call charges nothing. Wiro prorates an upgrade over the real length of
60
- * the billing period, which the 30-day estimate does not know, so an upgrade
61
- * is approved at its maximum: the full price difference, which the prorated
62
- * charge never exceeds.
157
+ * call charges nothing. An upgrade is approved at the most it can charge: the
158
+ * amount Wiro works out for a wallet plan, which only shrinks as the period
159
+ * runs on, or the full price difference when Wiro works the amount out only
160
+ * at the upgrade.
63
161
  */
64
162
  function approvalAmountOf(action, quote) {
65
163
  if (action !== 'upgrade_to_pro')
@@ -96,7 +194,7 @@ function changedLead(action, amount, approved, callerRequestId) {
96
194
  }
97
195
  return `${changed} Nothing was charged. Show the new amount to the user and ask again.`;
98
196
  }
99
- function planFor(client, action, agent, raw, input) {
197
+ async function planFor(client, action, agent, raw, input, signal) {
100
198
  const guid = agent.guid;
101
199
  const active = raw?.status === 'active';
102
200
  const prepaid = raw?.provider === 'prepaid';
@@ -130,7 +228,7 @@ function planFor(client, action, agent, raw, input) {
130
228
  tier,
131
229
  },
132
230
  message: `Subscribe on ${tierName(tier)}: ${formatUsd(price.priceUsd)} now from ${wallet}, then every 30 days until the plan is cancelled, with ${formatCredits(price.monthlyCredits)} credits per month. `
133
- + 'The agent is queued to start right after the payment.'
231
+ + subscribeQuoteNote(agent.status)
134
232
  + (missing.length > 0
135
233
  ? ` Missing credentials (${missing.map(item => item.title).join(', ')}) can be added afterwards; until then the skills that need them fail.`
136
234
  : ''),
@@ -144,9 +242,10 @@ function planFor(client, action, agent, raw, input) {
144
242
  const credits = toNumber(body.monthlycredits) ?? price.monthlyCredits;
145
243
  return {
146
244
  outcome: 'done',
147
- message: `Subscribed on ${tierName(tier)} and charged ${formatUsd(charged)} from ${wallet}. The plan renews every 30 days until it is cancelled. The agent is starting.`,
245
+ message: `Subscribed on ${tierName(tier)} and charged ${formatUsd(charged)} from ${wallet}. The plan renews every 30 days until it is cancelled. `
246
+ + (subscribeStarts(agent.status) ? 'The agent is starting.' : 'The agent keeps running: a new plan does not restart it.'),
148
247
  result: { chargedUsd: charged, monthlyPriceUsd: charged, monthlyCredits: credits, tier },
149
- nextAction: waitAction(guid, 'Wait until the agent is running.'),
248
+ nextAction: subscribeWait(agent),
150
249
  };
151
250
  },
152
251
  };
@@ -177,10 +276,10 @@ function planFor(client, action, agent, raw, input) {
177
276
  if (!cancelled) {
178
277
  return { outcome: 'not_needed', message: 'The plan is active and renews by itself. Nothing to do.' };
179
278
  }
180
- const amount = agent.subscription?.amountUsd ?? agent.monthlyPriceUsd;
279
+ const amount = agent.monthlyPriceUsd || agent.subscription?.amountUsd;
181
280
  return {
182
281
  outcome: 'quote',
183
- quote: { chargeNowUsd: 0, estimated: false, recurringUsd: amount, monthlyCredits: agent.credits.monthly, paidFrom },
282
+ quote: { chargeNowUsd: 0, estimated: false, recurringUsd: amount, monthlyCredits: agent.credits.renewal ?? agent.credits.monthly, paidFrom },
184
283
  message: `Undo the cancellation: nothing is charged now. The plan then renews from ${wallet} for ${formatUsd(amount)} after ${periodEnd} and every 30 days after that until it is cancelled.`,
185
284
  confirmReason: 'Ask the user to confirm first: the plan will charge the wallet again at each renewal.',
186
285
  perform: async (signal) => {
@@ -240,7 +339,7 @@ function planFor(client, action, agent, raw, input) {
240
339
  ...(credits !== null ? { monthlyCredits: credits } : {}),
241
340
  renewal: 'renewed',
242
341
  },
243
- nextAction: waitAction(guid, 'Wait until the agent is running again.'),
342
+ nextAction: renewWait(agent),
244
343
  };
245
344
  },
246
345
  };
@@ -258,16 +357,17 @@ function planFor(client, action, agent, raw, input) {
258
357
  };
259
358
  };
260
359
  if (!prepaid) {
261
- if (agent.workspace === 'personal') {
360
+ // A plan moving to wallet billing has no cancel in the dashboard either: it can be cancelled once the wallet bills it.
361
+ if (raw?.nextprovider === 'prepaid') {
262
362
  return {
263
363
  outcome: 'refused',
264
- message: `This plan is billed by card. Card plans are cancelled from the dashboard: ${agent.dashboardUrl}`,
364
+ message: `A switch to wallet billing is already scheduled for this plan, so it cannot be cancelled now. After ${periodEnd} it is billed from the wallet, and action cancel then stops its renewals.`,
265
365
  };
266
366
  }
267
- if (raw?.nextprovider === 'prepaid') {
367
+ if (agent.workspace === 'personal') {
268
368
  return {
269
369
  outcome: 'refused',
270
- message: 'A switch to wallet billing is already scheduled for this plan, so it cannot be cancelled here.',
370
+ message: `This plan is billed by card. Card plans are cancelled from the dashboard: ${agent.dashboardUrl}`,
271
371
  };
272
372
  }
273
373
  if (agent.subscription?.cancelAtPeriodEnd) {
@@ -294,6 +394,14 @@ function planFor(client, action, agent, raw, input) {
294
394
  }
295
395
  case 'upgrade_to_pro': {
296
396
  if (!active) {
397
+ // A Pro plan that ended comes back at its own price with renew. Subscribe takes today's price.
398
+ if (agent.tier === 'pro' && raw?.status === 'expired' && prepaid) {
399
+ return {
400
+ outcome: 'refused',
401
+ message: 'This agent is already on Pro, and its plan has ended. Action renew brings the plan back at its own price; subscribe starts a new plan at today\'s price.',
402
+ nextAction: billingCall(guid, 'renew'),
403
+ };
404
+ }
297
405
  return {
298
406
  outcome: 'refused',
299
407
  message: 'Upgrading needs an active plan. Use action subscribe with tier pro instead.',
@@ -305,32 +413,42 @@ function planFor(client, action, agent, raw, input) {
305
413
  // No checkout pages are opened for card plans: the user pays them in the dashboard.
306
414
  if (route === 'card')
307
415
  return { outcome: 'refused', message: cardPlan('upgrade it to Pro') };
308
- const pro = agent.pricing?.pro;
416
+ const preview = await readProPreview(client, guid, signal);
417
+ const pro = preview?.price ?? agent.pricing?.pro;
309
418
  if (!pro)
310
419
  throw new Error('The Pro price of this agent is not available right now. Try again later.');
311
- const ending = `The plan is cancelled, so it ends about a day after ${periodEnd} and does not renew at the Pro price; this charge covers only the days left. To keep the plan, undo the cancellation first with action renew.`;
312
- const current = agent.monthlyPriceUsd ?? agent.subscription?.amountUsd ?? 0;
313
- const periodEndSeconds = toNumber(raw?.currentperiodend);
314
- const daysLeft = periodEndSeconds !== null
315
- ? Math.max(0, Math.round((periodEndSeconds - Date.now() / 1000) / 86_400))
316
- : 30;
317
- const maxCharge = roundUsd(Math.max(0, pro.priceUsd - current));
318
- const estimate = roundUsd(Math.max(0, ((pro.priceUsd - current) / 30) * daysLeft));
420
+ const ending = `The plan is cancelled, so it ends about a day after ${periodEnd} and does not renew at the Pro price; this charge covers only the time left. To keep the plan, undo the cancellation first with action renew.`;
421
+ const current = agent.monthlyPriceUsd || agent.subscription?.amountUsd || 0;
422
+ const fullDifference = roundUsd(Math.max(0, pro.priceUsd - current));
423
+ // A wallet plan pays the amount Wiro works out on it now. A card plan moving to wallet billing is
424
+ // charged from the wallet for the time left of its card period, which Wiro works out at the upgrade.
425
+ const change = preview?.change?.wallet ? preview.change : null;
426
+ const exact = change?.chargeNowUsd ?? null;
427
+ const renewalCredits = change?.creditsFromRenewal ?? pro.monthlyCredits;
428
+ const creditsNow = change?.creditsNow ?? null;
429
+ const days = daysLeftText(change?.secondsLeft ?? null);
430
+ const money = exact !== null
431
+ ? `Upgrade to Pro: ${formatUsd(exact)} now from ${wallet} for the rest of this period`
432
+ + `${exact > 0 && underOneDayLeft(change?.secondsLeft ?? null) ? ', at the one-day minimum' : days ? ` (${days})` : ''}. `
433
+ : `Upgrade to Pro: the difference for the rest of this period is taken from ${wallet} now. Wiro works it out from the time left when the upgrade is applied, and it is never more than ${formatUsd(fullDifference)}, so that is the amount to approve. `;
434
+ const credits = creditsNow !== null
435
+ ? upgradeCreditsText(creditsNow, cancelled ? '' : `, and every renewal gives ${formatCredits(renewalCredits)}`, false)
436
+ : `The added credits come for the time left in this period${cancelled ? '' : `, and every renewal gives ${formatCredits(renewalCredits)}`}. `;
319
437
  return {
320
438
  outcome: 'quote',
321
439
  quote: {
322
- chargeNowUsd: estimate,
323
- estimated: true,
324
- maxChargeUsd: maxCharge,
440
+ chargeNowUsd: exact,
441
+ estimated: exact === null,
442
+ maxChargeUsd: exact ?? fullDifference,
325
443
  recurringUsd: cancelled ? null : pro.priceUsd,
326
- monthlyCredits: pro.monthlyCredits,
444
+ monthlyCredits: renewalCredits,
445
+ ...(creditsNow !== null ? { creditsNow } : {}),
327
446
  paidFrom,
328
447
  tier: 'pro',
329
448
  },
330
- message: `Upgrade to Pro: about ${formatUsd(estimate)} now from ${wallet} for the ${daysLeft} days left in this period. `
331
- + `Wiro works out the exact amount from the real length of the billing period, and it is never more than ${formatUsd(maxCharge)}, so that is the amount to approve. `
449
+ message: money + credits
332
450
  + (cancelled ? `${ending} ` : `From the next renewal the plan costs ${formatUsd(pro.priceUsd)} every 30 days. `)
333
- + `The monthly pool rises to ${formatCredits(pro.monthlyCredits)} credits at once and the agent restarts. To go back to Starter later, cancel, let the plan end, then subscribe to Starter.`,
451
+ + `${planChangeAgentNote(agent.status, 'the upgrade', true)} To go back to Starter later, cancel, let the plan end, then subscribe to Starter.`,
334
452
  perform: async (signal) => {
335
453
  const body = requireApiSuccess(await client.post('/UserAgent/UpgradeTier', { guid, targetTier: 'pro' }, signal), 'The plan could not be upgraded.');
336
454
  if (asString(body.url)) {
@@ -339,23 +457,28 @@ function planFor(client, action, agent, raw, input) {
339
457
  }
340
458
  const charged = toNumber(body.proratedCharge);
341
459
  const newPrice = toNumber(body.newPriceUsd) ?? pro.priceUsd;
342
- const newCredits = toNumber(body.newMonthlyCredits) ?? pro.monthlyCredits;
460
+ const fromRenewal = toNumber(body.creditsFromRenewal) ?? toNumber(body.newMonthlyCredits) ?? renewalCredits;
461
+ const came = toNumber(body.creditsNow);
343
462
  const previousTier = asString(body.previousTier);
344
463
  return {
345
464
  outcome: 'done',
346
465
  message: `Upgraded to Pro and charged ${formatUsd(charged ?? 0)} from ${wallet}. `
466
+ + (came !== null
467
+ ? upgradeCreditsText(came, cancelled ? '' : `, and every renewal gives ${formatCredits(fromRenewal)}`, true)
468
+ : '')
347
469
  + (cancelled
348
470
  ? `The plan is still cancelled and ends about a day after ${periodEnd}; action renew undoes the cancellation. `
349
471
  : `The plan now costs ${formatUsd(newPrice)} every 30 days. `)
350
- + `The monthly pool is ${formatCredits(newCredits)} credits. The agent restarts.`,
472
+ + planChangeAgentNote(agent.status, 'the upgrade'),
351
473
  result: {
352
474
  chargedUsd: charged,
353
475
  monthlyPriceUsd: newPrice,
354
- monthlyCredits: newCredits,
476
+ monthlyCredits: fromRenewal,
477
+ ...(came !== null ? { creditsNow: came } : {}),
355
478
  tier: 'pro',
356
479
  ...(previousTier ? { previousTier } : {}),
357
480
  },
358
- nextAction: waitAction(guid, 'Wait until the agent is running again.'),
481
+ nextAction: waitIfStarting(agent),
359
482
  };
360
483
  },
361
484
  };
@@ -378,6 +501,7 @@ function planFor(client, action, agent, raw, input) {
378
501
  if (!input.pack) {
379
502
  return {
380
503
  outcome: 'refused',
504
+ ...(packs.length > 0 ? { heading: 'Available packs' } : {}),
381
505
  message: packs.length > 0 ? `Choose a pack: ${packList}.` : 'No extra credit packs are available for this agent yet.',
382
506
  ...(packs[0] ? { nextAction: billingCall(guid, 'buy_credits', { pack: packs[0].pack }) } : {}),
383
507
  };
@@ -392,7 +516,7 @@ function planFor(client, action, agent, raw, input) {
392
516
  return {
393
517
  outcome: 'quote',
394
518
  quote: { chargeNowUsd: chosen.priceUsd, estimated: false, creditsAdded: chosen.credits, paidFrom, pack: chosen.pack },
395
- message: `Buy the ${chosen.pack} pack: ${formatUsd(chosen.priceUsd)} now from ${wallet} for ${formatCredits(chosen.credits)} extra credits, added at once. Extra credits are used after the monthly credits and do not reset each month. The agent restarts to pick them up. This is a one-time charge.`,
519
+ message: `Buy the ${chosen.pack} pack: ${formatUsd(chosen.priceUsd)} now from ${wallet} for ${formatCredits(chosen.credits)} extra credits, added at once. Extra credits are used after the monthly credits, do not reset each month and end 6 months (180 days) after purchase. ${planChangeAgentNote(agent.status, 'a credit pack', true)} This is a one-time charge.`,
396
520
  perform: async (signal) => {
397
521
  const body = requireApiSuccess(await client.post('/UserAgent/CreateExtraCreditCheckout', {
398
522
  useragentguid: guid,
@@ -403,9 +527,9 @@ function planFor(client, action, agent, raw, input) {
403
527
  return { outcome: 'refused', message: cardPlan('buy the pack') };
404
528
  return {
405
529
  outcome: 'done',
406
- message: `Bought the ${chosen.pack} pack: ${formatCredits(chosen.credits)} credits for ${formatUsd(chosen.priceUsd)} from ${wallet}. The credits are added and the agent restarts to pick them up.`,
530
+ message: `Bought the ${chosen.pack} pack: ${formatCredits(chosen.credits)} credits for ${formatUsd(chosen.priceUsd)} from ${wallet}. The credits are added. ${planChangeAgentNote(agent.status, 'a credit pack')}`,
407
531
  result: { chargedUsd: chosen.priceUsd, creditsAdded: chosen.credits },
408
- nextAction: waitAction(guid, 'Wait until the agent is running again.'),
532
+ nextAction: waitIfStarting(agent),
409
533
  };
410
534
  },
411
535
  };
@@ -464,7 +588,6 @@ async function observeOpenOutcome(client, action, before, quote, requestId, sent
464
588
  }
465
589
  const after = toAgentDetail(row);
466
590
  const charged = chargeOf(quote);
467
- const restart = waitAction(before.guid, 'Wait until the agent is running again.');
468
591
  switch (action) {
469
592
  case 'subscribe':
470
593
  return after.subscription?.status === 'active'
@@ -473,7 +596,7 @@ async function observeOpenOutcome(client, action, before, quote, requestId, sent
473
596
  row,
474
597
  text: 'The agent now has an active plan, so the payment went through. Do not subscribe again.',
475
598
  result: { chargedUsd: charged, ...(quote?.tier ? { tier: quote.tier } : {}) },
476
- nextAction: waitAction(before.guid, 'Wait until the agent is running.'),
599
+ nextAction: subscribeWait(before),
477
600
  }
478
601
  : { happened: false, text: 'The agent shows no active plan yet. Check get_my_agent again in a minute and the wallet balance with get_account before trying again; if the plan is still not active but the balance changed, contact Wiro support.' };
479
602
  case 'renew':
@@ -483,7 +606,7 @@ async function observeOpenOutcome(client, action, before, quote, requestId, sent
483
606
  row,
484
607
  text: 'The plan is active again, so the renewal went through. Do not renew it again.',
485
608
  result: { chargedUsd: charged, renewal: 'renewed' },
486
- nextAction: restart,
609
+ nextAction: renewWait(before),
487
610
  }
488
611
  : { happened: false, text: 'The plan still shows as expired. Check get_my_agent again in a minute and the wallet balance with get_account before renewing again; if the plan is still expired but the balance changed, contact Wiro support.' };
489
612
  case 'upgrade_to_pro': {
@@ -493,7 +616,7 @@ async function observeOpenOutcome(client, action, before, quote, requestId, sent
493
616
  row,
494
617
  text: `The agent is on Pro now, so the upgrade went through. Do not upgrade again. The prorated charge is not shown, and it is at most ${formatUsd(quote?.maxChargeUsd)}.`,
495
618
  result: { chargedUsd: null, tier: 'pro' },
496
- nextAction: restart,
619
+ nextAction: waitIfStarting(before),
497
620
  };
498
621
  }
499
622
  const proPrice = before.pricing?.pro.priceUsd;
@@ -516,7 +639,7 @@ async function observeOpenOutcome(client, action, before, quote, requestId, sent
516
639
  row,
517
640
  text: `The agent's extra credits rose by ${formatCredits(rose)}, so the purchase went through. Do not buy it again.`,
518
641
  result: bought,
519
- nextAction: restart,
642
+ nextAction: waitIfStarting(before),
520
643
  };
521
644
  }
522
645
  const fresh = await freshPackShows(client, before.guid, credits, sentAtMs, signal);
@@ -541,8 +664,15 @@ async function observeOpenOutcome(client, action, before, quote, requestId, sent
541
664
  return { happened: false, text: '' };
542
665
  }
543
666
  }
544
- function annotateKnownRefusal(error) {
667
+ // Code 104: another payment from the same wallet, or another change to the agent's plan, was going through.
668
+ const BUSY_NOTE = 'Nothing was charged or changed, and it is not a low balance. Offer to try again in a minute.';
669
+ function annotateKnownRefusal(error, requestId) {
545
670
  const text = error.message;
671
+ if (error.codes.some(code => Number(code) === 104)) {
672
+ return new Error(`${text}\n\n${BUSY_NOTE}${requestId ? ` To try again, repeat the call with request_id "${requestId}"; that request id never charges twice.` : ''}`);
673
+ }
674
+ if (isWalletNotRead(error))
675
+ return new Error(`${text}\n\n${WALLET_NOT_READ_NOTE}`);
546
676
  if (/insufficient wallet balance/i.test(text)) {
547
677
  return new Error(`${text}\n\nNothing was charged. Funds are added at https://wiro.ai/panel/wallet/deposit/credit-card (for a team project, select that team in the dashboard first); once the user has added them and says yes, try again. get_account shows the balance.`);
548
678
  }
@@ -560,19 +690,23 @@ export function registerManageAgentBilling(server, client) {
560
690
  description: 'Pay for, renew, upgrade or cancel a deployed agent\'s plan, or buy extra credits. Charges '
561
691
  + 'come from the Wiro wallet of the agent\'s workspace (the team wallet for a team agent); the connected '
562
692
  + 'project must belong to that same workspace. Every call without confirm: true only returns what will happen '
563
- + 'and the amount (for a prorated upgrade, an estimate and its maximum). Show it to the user, get '
693
+ + 'and the amount (for an upgrade, the prorated amount Wiro works out, or the most it can charge when '
694
+ + 'that is worked out only at the upgrade). Show it to the user, get '
564
695
  + 'their approval, then repeat the call with confirm: true and confirm_charge_usd set to the quoted '
565
- + 'amount (for an upgrade, its maximum); if the price changed in between, nothing is charged and a '
696
+ + 'amount (for an upgrade, quote.maxChargeUsd); if the price changed in between, nothing is charged and a '
566
697
  + 'new quote comes back. Never repeat a confirmed charge that failed or timed out without checking '
567
698
  + 'get_my_agent first. '
568
699
  + 'subscribe starts a plan for an agent without one; renew undoes a cancellation or renews an expired '
569
700
  + 'wallet plan; upgrade_to_pro charges the prorated difference now; buy_credits adds a credit pack '
570
- + '(Pro only); cancel stops renewals with no refund. Plans renew every 30 days until cancelled. An '
701
+ + '(Pro only); cancel stops renewals with no refund. Plans renew every 30 days until cancelled. Renewals, '
702
+ + 'upgrades and packs leave a running agent running. An '
571
703
  + 'agent whose plan is billed by card is upgraded and topped up in the Wiro dashboard, and a personal '
572
- + 'card plan is cancelled there; the tool returns the link and charges nothing. On a team agent only a team '
704
+ + 'card plan is cancelled there; the tool returns the link and charges nothing. A card plan that is moving to '
705
+ + 'wallet billing pays upgrade_to_pro and buy_credits from the wallet, and cancel works once the wallet bills it. '
706
+ + 'On a team agent only a team '
573
707
  + 'admin, the organization\'s owner or the person who deployed the agent can pay for it or change its plan; '
574
708
  + 'other members are refused and nothing is charged.',
575
- inputSchema: {
709
+ inputSchema: strictInput({
576
710
  agent_guid: z.string().min(1).max(100)
577
711
  .describe('The deployed agent\'s guid, from list_my_agents or deploy_agent.'),
578
712
  action: z.enum(ACTIONS)
@@ -587,7 +721,7 @@ export function registerManageAgentBilling(server, client) {
587
721
  .describe('With confirm: true, the amount the user approved, from the quote\'s nextAction (quote.chargeNowUsd, or quote.maxChargeUsd for upgrade_to_pro). Required for every action that charges: subscribe, upgrade_to_pro, buy_credits and renewing an expired plan. If a fresh quote no longer matches it, nothing is charged and the new quote is returned. For upgrade_to_pro it is the most the user agreed to pay now.'),
588
722
  confirm: z.boolean().default(false)
589
723
  .describe('false returns only the quote. true performs the action. Set it only after the user approved it.'),
590
- },
724
+ }),
591
725
  outputSchema: {
592
726
  action: z.enum(ACTIONS),
593
727
  agentGuid: z.string(),
@@ -618,7 +752,7 @@ export function registerManageAgentBilling(server, client) {
618
752
  const summary = toAgentSummary(payload.agentRow);
619
753
  const summaryLines = formatAgentSummaryLines(summary, plainMember);
620
754
  const dashboardUrl = before?.dashboardUrl ?? agentDashboardUrl(guid);
621
- const heading = {
755
+ const heading = payload.heading ?? {
622
756
  quote: 'Quote (nothing done yet)',
623
757
  done: 'Done',
624
758
  not_needed: 'Nothing to do',
@@ -657,9 +791,9 @@ export function registerManageAgentBilling(server, client) {
657
791
  before = agent;
658
792
  plainMember = await isPlainTeamMember(client, row, extra.signal);
659
793
  const raw = isPlainObject(row.subscription) ? row.subscription : null;
660
- plan = planFor(client, action, agent, raw, { tier, pack, requestId: requestId ?? '' });
794
+ plan = await planFor(client, action, agent, raw, { tier, pack, requestId: requestId ?? '' }, extra.signal);
661
795
  if (plan.outcome !== 'quote' || !plan.perform) {
662
- return respond({ outcome: plan.outcome, message: plan.message, nextAction: plan.nextAction, agentRow: row });
796
+ return respond({ outcome: plan.outcome, heading: plan.heading, message: plan.message, nextAction: plan.nextAction, agentRow: row });
663
797
  }
664
798
  // A team member without admin rights can still be the person who
665
799
  // deployed the agent, who may pay for it, so Wiro is asked before a quote.
@@ -728,6 +862,20 @@ export function registerManageAgentBilling(server, client) {
728
862
  agentRow: after,
729
863
  });
730
864
  }
865
+ // A repeat adds no credits. A new pack can add none only when an older
866
+ // pack ends at the same moment with at least as many left, so an answer
867
+ // with no growth says only what holds in both cases.
868
+ if (fresh && extraAfter <= extraBefore) {
869
+ return respond({
870
+ outcome: 'done',
871
+ heading: 'Pack in place',
872
+ message: `The ${plan.quote?.pack} pack is in place: ${formatCredits(credits)} credits for ${formatUsd(plan.quote?.chargeNowUsd)} from ${agent.workspace === 'team' ? 'the team wallet' : 'the personal wallet'}. This request id is charged only once. The agent's extra credits did not grow in this call, so if the id had already completed in an earlier call, nothing new was bought or charged now. ${planChangeAgentNote(agent.status, 'a credit pack')} For another pack, get a new quote without request_id.`,
873
+ quote: plan.quote,
874
+ result: { chargedUsd: null },
875
+ nextAction: performed.nextAction,
876
+ agentRow: after,
877
+ });
878
+ }
731
879
  return respond({
732
880
  outcome: 'done',
733
881
  message: fresh
@@ -780,7 +928,7 @@ export function registerManageAgentBilling(server, client) {
780
928
  return errorResult(new Error(`${text}\n\nThe request was sent and the answer does not show whether it went through. ${observed.text} ${retry}`));
781
929
  }
782
930
  if (error instanceof ApiResultError)
783
- return errorResult(annotateKnownRefusal(error));
931
+ return errorResult(annotateKnownRefusal(error, sentAtMs > 0 ? requestId : undefined));
784
932
  return errorResult(error);
785
933
  }
786
934
  });