@wiro-ai/wiro-mcp 1.4.0 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (248) hide show
  1. package/README.md +50 -17
  2. package/dist/client.d.ts +25 -6
  3. package/dist/client.d.ts.map +1 -1
  4. package/dist/client.js +129 -29
  5. package/dist/client.js.map +1 -1
  6. package/dist/instructions.d.ts.map +1 -1
  7. package/dist/instructions.js +24 -17
  8. package/dist/instructions.js.map +1 -1
  9. package/dist/playbooks.d.ts.map +1 -1
  10. package/dist/playbooks.js +61 -48
  11. package/dist/playbooks.js.map +1 -1
  12. package/dist/prompts.d.ts.map +1 -1
  13. package/dist/prompts.js +4 -3
  14. package/dist/prompts.js.map +1 -1
  15. package/dist/server.d.ts +6 -0
  16. package/dist/server.d.ts.map +1 -1
  17. package/dist/server.js +3 -4
  18. package/dist/server.js.map +1 -1
  19. package/dist/tools/apply-agent-skills.d.ts.map +1 -1
  20. package/dist/tools/apply-agent-skills.js +400 -88
  21. package/dist/tools/apply-agent-skills.js.map +1 -1
  22. package/dist/tools/bind-agent-project.d.ts +4 -0
  23. package/dist/tools/bind-agent-project.d.ts.map +1 -0
  24. package/dist/tools/bind-agent-project.js +403 -0
  25. package/dist/tools/bind-agent-project.js.map +1 -0
  26. package/dist/tools/cancel-agent-message.d.ts.map +1 -1
  27. package/dist/tools/cancel-agent-message.js +6 -3
  28. package/dist/tools/cancel-agent-message.js.map +1 -1
  29. package/dist/tools/cancel-task.js +3 -3
  30. package/dist/tools/cancel-task.js.map +1 -1
  31. package/dist/tools/clone-workflow.d.ts.map +1 -1
  32. package/dist/tools/clone-workflow.js +5 -4
  33. package/dist/tools/clone-workflow.js.map +1 -1
  34. package/dist/tools/connect-agent-oauth.d.ts.map +1 -1
  35. package/dist/tools/connect-agent-oauth.js +144 -44
  36. package/dist/tools/connect-agent-oauth.js.map +1 -1
  37. package/dist/tools/control-agent.d.ts.map +1 -1
  38. package/dist/tools/control-agent.js +133 -45
  39. package/dist/tools/control-agent.js.map +1 -1
  40. package/dist/tools/create-workflow.d.ts +9 -6
  41. package/dist/tools/create-workflow.d.ts.map +1 -1
  42. package/dist/tools/create-workflow.js +13 -13
  43. package/dist/tools/create-workflow.js.map +1 -1
  44. package/dist/tools/delete-agent-session.d.ts.map +1 -1
  45. package/dist/tools/delete-agent-session.js +20 -14
  46. package/dist/tools/delete-agent-session.js.map +1 -1
  47. package/dist/tools/delete-agent.d.ts.map +1 -1
  48. package/dist/tools/delete-agent.js +14 -10
  49. package/dist/tools/delete-agent.js.map +1 -1
  50. package/dist/tools/delete-workflow.d.ts.map +1 -1
  51. package/dist/tools/delete-workflow.js +6 -5
  52. package/dist/tools/delete-workflow.js.map +1 -1
  53. package/dist/tools/deploy-agent.d.ts.map +1 -1
  54. package/dist/tools/deploy-agent.js +215 -47
  55. package/dist/tools/deploy-agent.js.map +1 -1
  56. package/dist/tools/disconnect-agent-oauth.d.ts.map +1 -1
  57. package/dist/tools/disconnect-agent-oauth.js +32 -12
  58. package/dist/tools/disconnect-agent-oauth.js.map +1 -1
  59. package/dist/tools/get-account.d.ts.map +1 -1
  60. package/dist/tools/get-account.js +157 -49
  61. package/dist/tools/get-account.js.map +1 -1
  62. package/dist/tools/get-agent-activity.d.ts.map +1 -1
  63. package/dist/tools/get-agent-activity.js +186 -36
  64. package/dist/tools/get-agent-activity.js.map +1 -1
  65. package/dist/tools/get-agent-conversation.d.ts.map +1 -1
  66. package/dist/tools/get-agent-conversation.js +65 -39
  67. package/dist/tools/get-agent-conversation.js.map +1 -1
  68. package/dist/tools/get-agent-message.d.ts.map +1 -1
  69. package/dist/tools/get-agent-message.js +6 -4
  70. package/dist/tools/get-agent-message.js.map +1 -1
  71. package/dist/tools/get-agent-template.d.ts.map +1 -1
  72. package/dist/tools/get-agent-template.js +33 -17
  73. package/dist/tools/get-agent-template.js.map +1 -1
  74. package/dist/tools/get-model-schema.d.ts.map +1 -1
  75. package/dist/tools/get-model-schema.js +7 -5
  76. package/dist/tools/get-model-schema.js.map +1 -1
  77. package/dist/tools/get-my-agent.d.ts.map +1 -1
  78. package/dist/tools/get-my-agent.js +54 -11
  79. package/dist/tools/get-my-agent.js.map +1 -1
  80. package/dist/tools/get-playbook.d.ts.map +1 -1
  81. package/dist/tools/get-playbook.js +3 -2
  82. package/dist/tools/get-playbook.js.map +1 -1
  83. package/dist/tools/get-task-price.d.ts +1 -1
  84. package/dist/tools/get-task-price.d.ts.map +1 -1
  85. package/dist/tools/get-task-price.js +29 -8
  86. package/dist/tools/get-task-price.js.map +1 -1
  87. package/dist/tools/get-task.d.ts.map +1 -1
  88. package/dist/tools/get-task.js +25 -9
  89. package/dist/tools/get-task.js.map +1 -1
  90. package/dist/tools/get-workflow.d.ts.map +1 -1
  91. package/dist/tools/get-workflow.js +46 -21
  92. package/dist/tools/get-workflow.js.map +1 -1
  93. package/dist/tools/index.d.ts +4 -1
  94. package/dist/tools/index.d.ts.map +1 -1
  95. package/dist/tools/index.js +10 -1
  96. package/dist/tools/index.js.map +1 -1
  97. package/dist/tools/kill-task.js +3 -3
  98. package/dist/tools/kill-task.js.map +1 -1
  99. package/dist/tools/list-agent-skills.d.ts.map +1 -1
  100. package/dist/tools/list-agent-skills.js +5 -4
  101. package/dist/tools/list-agent-skills.js.map +1 -1
  102. package/dist/tools/list-agent-templates.js +4 -4
  103. package/dist/tools/list-agent-templates.js.map +1 -1
  104. package/dist/tools/list-my-agents.d.ts.map +1 -1
  105. package/dist/tools/list-my-agents.js +14 -8
  106. package/dist/tools/list-my-agents.js.map +1 -1
  107. package/dist/tools/list-projects.d.ts +4 -0
  108. package/dist/tools/list-projects.d.ts.map +1 -0
  109. package/dist/tools/list-projects.js +141 -0
  110. package/dist/tools/list-projects.js.map +1 -0
  111. package/dist/tools/list-studio-results.js +3 -3
  112. package/dist/tools/list-studio-results.js.map +1 -1
  113. package/dist/tools/list-tasks.js +3 -3
  114. package/dist/tools/list-tasks.js.map +1 -1
  115. package/dist/tools/list-workflow-runs.d.ts.map +1 -1
  116. package/dist/tools/list-workflow-runs.js +6 -4
  117. package/dist/tools/list-workflow-runs.js.map +1 -1
  118. package/dist/tools/list-workflows.d.ts.map +1 -1
  119. package/dist/tools/list-workflows.js +7 -6
  120. package/dist/tools/list-workflows.js.map +1 -1
  121. package/dist/tools/manage-agent-billing.d.ts.map +1 -1
  122. package/dist/tools/manage-agent-billing.js +259 -87
  123. package/dist/tools/manage-agent-billing.js.map +1 -1
  124. package/dist/tools/manage-agent-custom-skill.d.ts.map +1 -1
  125. package/dist/tools/manage-agent-custom-skill.js +91 -21
  126. package/dist/tools/manage-agent-custom-skill.js.map +1 -1
  127. package/dist/tools/recommend-model.js +3 -3
  128. package/dist/tools/recommend-model.js.map +1 -1
  129. package/dist/tools/resume-workflow-run.d.ts.map +1 -1
  130. package/dist/tools/resume-workflow-run.js +3 -2
  131. package/dist/tools/resume-workflow-run.js.map +1 -1
  132. package/dist/tools/run-model.js +5 -5
  133. package/dist/tools/run-model.js.map +1 -1
  134. package/dist/tools/run-workflow.d.ts.map +1 -1
  135. package/dist/tools/run-workflow.js +3 -2
  136. package/dist/tools/run-workflow.js.map +1 -1
  137. package/dist/tools/save-workflow-from-agent.d.ts.map +1 -1
  138. package/dist/tools/save-workflow-from-agent.js +22 -20
  139. package/dist/tools/save-workflow-from-agent.js.map +1 -1
  140. package/dist/tools/schemas.d.ts +150 -127
  141. package/dist/tools/schemas.d.ts.map +1 -1
  142. package/dist/tools/schemas.js +76 -3
  143. package/dist/tools/schemas.js.map +1 -1
  144. package/dist/tools/search-docs.js +3 -3
  145. package/dist/tools/search-docs.js.map +1 -1
  146. package/dist/tools/search-models.d.ts.map +1 -1
  147. package/dist/tools/search-models.js +7 -4
  148. package/dist/tools/search-models.js.map +1 -1
  149. package/dist/tools/send-agent-message.d.ts.map +1 -1
  150. package/dist/tools/send-agent-message.js +32 -11
  151. package/dist/tools/send-agent-message.js.map +1 -1
  152. package/dist/tools/set-agent-credentials.d.ts.map +1 -1
  153. package/dist/tools/set-agent-credentials.js +60 -59
  154. package/dist/tools/set-agent-credentials.js.map +1 -1
  155. package/dist/tools/update-agent.d.ts.map +1 -1
  156. package/dist/tools/update-agent.js +59 -21
  157. package/dist/tools/update-agent.js.map +1 -1
  158. package/dist/tools/update-workflow.d.ts +205 -6
  159. package/dist/tools/update-workflow.d.ts.map +1 -1
  160. package/dist/tools/update-workflow.js +20 -7
  161. package/dist/tools/update-workflow.js.map +1 -1
  162. package/dist/tools/upload-file.d.ts.map +1 -1
  163. package/dist/tools/upload-file.js +13 -9
  164. package/dist/tools/upload-file.js.map +1 -1
  165. package/dist/tools/wait-for-task.d.ts.map +1 -1
  166. package/dist/tools/wait-for-task.js +23 -5
  167. package/dist/tools/wait-for-task.js.map +1 -1
  168. package/dist/tools/wait-for-workflow-run.d.ts +18 -18
  169. package/dist/tools/wait-for-workflow-run.d.ts.map +1 -1
  170. package/dist/tools/wait-for-workflow-run.js +7 -6
  171. package/dist/tools/wait-for-workflow-run.js.map +1 -1
  172. package/dist/types.d.ts +6 -0
  173. package/dist/types.d.ts.map +1 -1
  174. package/dist/types.js.map +1 -1
  175. package/dist/utils/agent-catalog.d.ts +168 -139
  176. package/dist/utils/agent-catalog.d.ts.map +1 -1
  177. package/dist/utils/agent-catalog.js +34 -14
  178. package/dist/utils/agent-catalog.js.map +1 -1
  179. package/dist/utils/agent-config.d.ts +111 -8
  180. package/dist/utils/agent-config.d.ts.map +1 -1
  181. package/dist/utils/agent-config.js +290 -40
  182. package/dist/utils/agent-config.js.map +1 -1
  183. package/dist/utils/agent-message.d.ts +41 -22
  184. package/dist/utils/agent-message.d.ts.map +1 -1
  185. package/dist/utils/agent-message.js +94 -31
  186. package/dist/utils/agent-message.js.map +1 -1
  187. package/dist/utils/agent-targets.d.ts +8 -4
  188. package/dist/utils/agent-targets.d.ts.map +1 -1
  189. package/dist/utils/agent-targets.js +51 -35
  190. package/dist/utils/agent-targets.js.map +1 -1
  191. package/dist/utils/agent-turns.d.ts +31 -24
  192. package/dist/utils/agent-turns.d.ts.map +1 -1
  193. package/dist/utils/agent-turns.js +154 -35
  194. package/dist/utils/agent-turns.js.map +1 -1
  195. package/dist/utils/api.d.ts +6 -0
  196. package/dist/utils/api.d.ts.map +1 -1
  197. package/dist/utils/api.js +22 -1
  198. package/dist/utils/api.js.map +1 -1
  199. package/dist/utils/format.d.ts +27 -1
  200. package/dist/utils/format.d.ts.map +1 -1
  201. package/dist/utils/format.js +429 -82
  202. package/dist/utils/format.js.map +1 -1
  203. package/dist/utils/json-schema-dialect.d.ts +8 -1
  204. package/dist/utils/json-schema-dialect.d.ts.map +1 -1
  205. package/dist/utils/json-schema-dialect.js +27 -1
  206. package/dist/utils/json-schema-dialect.js.map +1 -1
  207. package/dist/utils/media.d.ts +8 -0
  208. package/dist/utils/media.d.ts.map +1 -1
  209. package/dist/utils/media.js +29 -0
  210. package/dist/utils/media.js.map +1 -1
  211. package/dist/utils/poll.d.ts +5 -2
  212. package/dist/utils/poll.d.ts.map +1 -1
  213. package/dist/utils/poll.js +10 -9
  214. package/dist/utils/poll.js.map +1 -1
  215. package/dist/utils/quoted-text.d.ts +28 -0
  216. package/dist/utils/quoted-text.d.ts.map +1 -0
  217. package/dist/utils/quoted-text.js +64 -0
  218. package/dist/utils/quoted-text.js.map +1 -0
  219. package/dist/utils/structured.d.ts +2 -0
  220. package/dist/utils/structured.d.ts.map +1 -1
  221. package/dist/utils/structured.js +15 -2
  222. package/dist/utils/structured.js.map +1 -1
  223. package/dist/utils/studio-runs.d.ts +4 -4
  224. package/dist/utils/studio-runs.d.ts.map +1 -1
  225. package/dist/utils/studio-runs.js +11 -8
  226. package/dist/utils/studio-runs.js.map +1 -1
  227. package/dist/utils/user-agent.d.ts +376 -241
  228. package/dist/utils/user-agent.d.ts.map +1 -1
  229. package/dist/utils/user-agent.js +302 -89
  230. package/dist/utils/user-agent.js.map +1 -1
  231. package/dist/utils/wiro-project.d.ts +45 -0
  232. package/dist/utils/wiro-project.d.ts.map +1 -0
  233. package/dist/utils/wiro-project.js +66 -0
  234. package/dist/utils/wiro-project.js.map +1 -0
  235. package/dist/utils/workflow-agent.d.ts +4 -4
  236. package/dist/utils/workflow-save.d.ts +23 -5
  237. package/dist/utils/workflow-save.d.ts.map +1 -1
  238. package/dist/utils/workflow-save.js +130 -38
  239. package/dist/utils/workflow-save.js.map +1 -1
  240. package/dist/utils/workflow.d.ts +79 -61
  241. package/dist/utils/workflow.d.ts.map +1 -1
  242. package/dist/utils/workflow.js +149 -19
  243. package/dist/utils/workflow.js.map +1 -1
  244. package/dist/utils/workspace.d.ts +64 -0
  245. package/dist/utils/workspace.d.ts.map +1 -0
  246. package/dist/utils/workspace.js +173 -0
  247. package/dist/utils/workspace.js.map +1 -0
  248. package/package.json +6 -4
@@ -1,10 +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
- import { AMOUNT_TOLERANCE_USD, agentDashboardUrl, agentSummarySchema, asTier, chargeRoute, formatAgentSummaryLines, formatCredits, formatUsd, outcomeUnknown, readAgentDetail, roundUsd, sameAmount, toAgentDetail, toAgentSummary, } from '../utils/user-agent.js';
7
- import { nextActionSchema } from './schemas.js';
6
+ import { isPlainTeamMember, teamAgentBillingRefused } from '../utils/workspace.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';
8
9
  const ACTIONS = ['subscribe', 'upgrade_to_pro', 'cancel', 'renew', 'buy_credits'];
9
10
  const quoteSchema = z.object({
10
11
  chargeNowUsd: z.number().nullable(),
@@ -12,6 +13,8 @@ const quoteSchema = z.object({
12
13
  maxChargeUsd: z.number().optional(),
13
14
  recurringUsd: z.number().nullable().optional(),
14
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.'),
15
18
  creditsAdded: z.number().optional(),
16
19
  paidFrom: z.enum(['personal_wallet', 'team_wallet', 'card']),
17
20
  tier: z.enum(['starter', 'pro']).optional(),
@@ -21,6 +24,8 @@ const resultSchema = z.object({
21
24
  chargedUsd: z.number().nullable().optional(),
22
25
  monthlyPriceUsd: z.number().optional(),
23
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.'),
24
29
  creditsAdded: z.number().optional(),
25
30
  tier: z.string().optional(),
26
31
  previousTier: z.string().optional(),
@@ -45,20 +50,114 @@ function waitAction(guid, reason) {
45
50
  reason,
46
51
  };
47
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
+ }
48
147
  function tierName(tier) {
49
148
  return tier === 'pro' ? 'Pro' : 'Starter';
50
149
  }
51
- /** 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. */
52
151
  function chargeOf(quote) {
53
- const amount = quote?.chargeNowUsd ?? null;
152
+ const amount = quote?.chargeNowUsd ?? quote?.maxChargeUsd ?? null;
54
153
  return amount !== null && amount > 0 ? amount : null;
55
154
  }
56
155
  /**
57
156
  * The amount the user approves with confirm_charge_usd, or null when the
58
- * call charges nothing. Wiro prorates an upgrade over the real length of
59
- * the billing period, which the 30-day estimate does not know, so an upgrade
60
- * is approved at its maximum: the full price difference, which the prorated
61
- * 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.
62
161
  */
63
162
  function approvalAmountOf(action, quote) {
64
163
  if (action !== 'upgrade_to_pro')
@@ -91,11 +190,11 @@ function changedLead(action, amount, approved, callerRequestId) {
91
190
  if (action === 'buy_credits' && callerRequestId) {
92
191
  // Wiro binds a purchase request id to the price of its first attempt.
93
192
  return `${changed} Nothing was charged by this call. Show the new amount to the user and ask again. `
94
- + `If an earlier call with request_id "${callerRequestId}" said the wallet was charged but the purchase is still pending, that purchase can only be finished at its original price, and Wiro refuses this request id at the new one. Do not approve the new amount for it; ask Wiro support to finish it.`;
193
+ + `If an earlier call with request_id "${callerRequestId}" ended without a clear result, do not approve the new amount for it: this request id works only at its original price. Check get_my_agent and the wallet balance with get_account, and if the balance changed, contact Wiro support.`;
95
194
  }
96
195
  return `${changed} Nothing was charged. Show the new amount to the user and ask again.`;
97
196
  }
98
- function planFor(client, action, agent, raw, input) {
197
+ async function planFor(client, action, agent, raw, input, signal) {
99
198
  const guid = agent.guid;
100
199
  const active = raw?.status === 'active';
101
200
  const prepaid = raw?.provider === 'prepaid';
@@ -129,7 +228,7 @@ function planFor(client, action, agent, raw, input) {
129
228
  tier,
130
229
  },
131
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. `
132
- + 'The agent is queued to start right after the payment.'
231
+ + subscribeQuoteNote(agent.status)
133
232
  + (missing.length > 0
134
233
  ? ` Missing credentials (${missing.map(item => item.title).join(', ')}) can be added afterwards; until then the skills that need them fail.`
135
234
  : ''),
@@ -143,9 +242,10 @@ function planFor(client, action, agent, raw, input) {
143
242
  const credits = toNumber(body.monthlycredits) ?? price.monthlyCredits;
144
243
  return {
145
244
  outcome: 'done',
146
- 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.'),
147
247
  result: { chargedUsd: charged, monthlyPriceUsd: charged, monthlyCredits: credits, tier },
148
- nextAction: waitAction(guid, 'Wait until the agent is running.'),
248
+ nextAction: subscribeWait(agent),
149
249
  };
150
250
  },
151
251
  };
@@ -160,23 +260,26 @@ function planFor(client, action, agent, raw, input) {
160
260
  }
161
261
  if (active) {
162
262
  if (!prepaid) {
163
- // A card plan can be set to stop at its period end, by its payer or when the
164
- // payer's account is deleted.
263
+ // A card plan can be set to stop at its period end.
264
+ const atPeriodEnd = agent.subscription?.atPeriodEnd;
165
265
  return {
166
266
  outcome: 'refused',
167
- message: agent.subscription?.cancelAtPeriodEnd
267
+ message: atPeriodEnd === 'ends'
168
268
  ? `This plan is billed by card and is set to stop renewing after ${periodEnd}. Card plans are managed `
169
269
  + `from the dashboard, where it can be kept: ${agent.dashboardUrl}`
170
- : `This plan is billed by card and renews by itself. It is managed from the dashboard: ${agent.dashboardUrl}`,
270
+ : atPeriodEnd === 'renews'
271
+ ? `This plan is billed by card and renews by itself. It is managed from the dashboard: ${agent.dashboardUrl}`
272
+ : `This plan is billed by card and is paid through ${periodEnd}. Card plans are renewed or cancelled `
273
+ + `from the dashboard: ${agent.dashboardUrl}`,
171
274
  };
172
275
  }
173
276
  if (!cancelled) {
174
277
  return { outcome: 'not_needed', message: 'The plan is active and renews by itself. Nothing to do.' };
175
278
  }
176
- const amount = agent.subscription?.amountUsd ?? agent.monthlyPriceUsd;
279
+ const amount = agent.monthlyPriceUsd || agent.subscription?.amountUsd;
177
280
  return {
178
281
  outcome: 'quote',
179
- 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 },
180
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.`,
181
284
  confirmReason: 'Ask the user to confirm first: the plan will charge the wallet again at each renewal.',
182
285
  perform: async (signal) => {
@@ -236,7 +339,7 @@ function planFor(client, action, agent, raw, input) {
236
339
  ...(credits !== null ? { monthlyCredits: credits } : {}),
237
340
  renewal: 'renewed',
238
341
  },
239
- nextAction: waitAction(guid, 'Wait until the agent is running again.'),
342
+ nextAction: renewWait(agent),
240
343
  };
241
344
  },
242
345
  };
@@ -254,16 +357,17 @@ function planFor(client, action, agent, raw, input) {
254
357
  };
255
358
  };
256
359
  if (!prepaid) {
257
- 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') {
258
362
  return {
259
363
  outcome: 'refused',
260
- 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.`,
261
365
  };
262
366
  }
263
- if (raw?.nextprovider === 'prepaid') {
367
+ if (agent.workspace === 'personal') {
264
368
  return {
265
369
  outcome: 'refused',
266
- 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}`,
267
371
  };
268
372
  }
269
373
  if (agent.subscription?.cancelAtPeriodEnd) {
@@ -290,6 +394,14 @@ function planFor(client, action, agent, raw, input) {
290
394
  }
291
395
  case 'upgrade_to_pro': {
292
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
+ }
293
405
  return {
294
406
  outcome: 'refused',
295
407
  message: 'Upgrading needs an active plan. Use action subscribe with tier pro instead.',
@@ -301,57 +413,72 @@ function planFor(client, action, agent, raw, input) {
301
413
  // No checkout pages are opened for card plans: the user pays them in the dashboard.
302
414
  if (route === 'card')
303
415
  return { outcome: 'refused', message: cardPlan('upgrade it to Pro') };
304
- const pro = agent.pricing?.pro;
416
+ const preview = await readProPreview(client, guid, signal);
417
+ const pro = preview?.price ?? agent.pricing?.pro;
305
418
  if (!pro)
306
419
  throw new Error('The Pro price of this agent is not available right now. Try again later.');
307
- 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.`;
308
- const current = agent.monthlyPriceUsd ?? agent.subscription?.amountUsd ?? 0;
309
- const periodEndSeconds = toNumber(raw?.currentperiodend);
310
- const daysLeft = periodEndSeconds !== null
311
- ? Math.max(0, Math.round((periodEndSeconds - Date.now() / 1000) / 86_400))
312
- : 30;
313
- const maxCharge = roundUsd(Math.max(0, pro.priceUsd - current));
314
- 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)}`}. `;
315
437
  return {
316
438
  outcome: 'quote',
317
439
  quote: {
318
- chargeNowUsd: estimate,
319
- estimated: true,
320
- maxChargeUsd: maxCharge,
440
+ chargeNowUsd: exact,
441
+ estimated: exact === null,
442
+ maxChargeUsd: exact ?? fullDifference,
321
443
  recurringUsd: cancelled ? null : pro.priceUsd,
322
- monthlyCredits: pro.monthlyCredits,
444
+ monthlyCredits: renewalCredits,
445
+ ...(creditsNow !== null ? { creditsNow } : {}),
323
446
  paidFrom,
324
447
  tier: 'pro',
325
448
  },
326
- message: `Upgrade to Pro: about ${formatUsd(estimate)} now from ${wallet} for the ${daysLeft} days left in this period. `
327
- + `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
328
450
  + (cancelled ? `${ending} ` : `From the next renewal the plan costs ${formatUsd(pro.priceUsd)} every 30 days. `)
329
- + `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.`,
330
452
  perform: async (signal) => {
331
453
  const body = requireApiSuccess(await client.post('/UserAgent/UpgradeTier', { guid, targetTier: 'pro' }, signal), 'The plan could not be upgraded.');
332
454
  if (asString(body.url)) {
333
- // A checkout link means the plan is paid by card. The link is a payment page for the owner's card, so it is not passed on.
455
+ // A checkout link means the plan is paid by card, and card payments are made in the dashboard.
334
456
  return { outcome: 'refused', message: cardPlan('upgrade it to Pro') };
335
457
  }
336
458
  const charged = toNumber(body.proratedCharge);
337
459
  const newPrice = toNumber(body.newPriceUsd) ?? pro.priceUsd;
338
- const newCredits = toNumber(body.newMonthlyCredits) ?? pro.monthlyCredits;
460
+ const fromRenewal = toNumber(body.creditsFromRenewal) ?? toNumber(body.newMonthlyCredits) ?? renewalCredits;
461
+ const came = toNumber(body.creditsNow);
339
462
  const previousTier = asString(body.previousTier);
340
463
  return {
341
464
  outcome: 'done',
342
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
+ : '')
343
469
  + (cancelled
344
470
  ? `The plan is still cancelled and ends about a day after ${periodEnd}; action renew undoes the cancellation. `
345
471
  : `The plan now costs ${formatUsd(newPrice)} every 30 days. `)
346
- + `The monthly pool is ${formatCredits(newCredits)} credits. The agent restarts.`,
472
+ + planChangeAgentNote(agent.status, 'the upgrade'),
347
473
  result: {
348
474
  chargedUsd: charged,
349
475
  monthlyPriceUsd: newPrice,
350
- monthlyCredits: newCredits,
476
+ monthlyCredits: fromRenewal,
477
+ ...(came !== null ? { creditsNow: came } : {}),
351
478
  tier: 'pro',
352
479
  ...(previousTier ? { previousTier } : {}),
353
480
  },
354
- nextAction: waitAction(guid, 'Wait until the agent is running again.'),
481
+ nextAction: waitIfStarting(agent),
355
482
  };
356
483
  },
357
484
  };
@@ -374,6 +501,7 @@ function planFor(client, action, agent, raw, input) {
374
501
  if (!input.pack) {
375
502
  return {
376
503
  outcome: 'refused',
504
+ ...(packs.length > 0 ? { heading: 'Available packs' } : {}),
377
505
  message: packs.length > 0 ? `Choose a pack: ${packList}.` : 'No extra credit packs are available for this agent yet.',
378
506
  ...(packs[0] ? { nextAction: billingCall(guid, 'buy_credits', { pack: packs[0].pack }) } : {}),
379
507
  };
@@ -388,7 +516,7 @@ function planFor(client, action, agent, raw, input) {
388
516
  return {
389
517
  outcome: 'quote',
390
518
  quote: { chargeNowUsd: chosen.priceUsd, estimated: false, creditsAdded: chosen.credits, paidFrom, pack: chosen.pack },
391
- 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.`,
392
520
  perform: async (signal) => {
393
521
  const body = requireApiSuccess(await client.post('/UserAgent/CreateExtraCreditCheckout', {
394
522
  useragentguid: guid,
@@ -399,9 +527,9 @@ function planFor(client, action, agent, raw, input) {
399
527
  return { outcome: 'refused', message: cardPlan('buy the pack') };
400
528
  return {
401
529
  outcome: 'done',
402
- 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')}`,
403
531
  result: { chargedUsd: chosen.priceUsd, creditsAdded: chosen.credits },
404
- nextAction: waitAction(guid, 'Wait until the agent is running again.'),
532
+ nextAction: waitIfStarting(agent),
405
533
  };
406
534
  },
407
535
  };
@@ -423,8 +551,8 @@ function confirmArguments(guid, action, quote, requestId) {
423
551
  }
424
552
  function retryPurchaseText(guid, quote, requestId) {
425
553
  const amount = chargeOf(quote);
426
- return `To retry, call manage_agent_billing again with agent_guid "${guid}", action buy_credits, pack "${quote?.pack ?? ''}", request_id "${requestId}"${amount !== null ? `, confirm_charge_usd ${amount}` : ''} and confirm: true. That request id never charges twice, and it finishes a purchase that was charged but not completed. Do not get a new quote for this purchase: a new request id is a new purchase.`
427
- + (amount !== null ? ` This works only while the pack still costs ${formatUsd(amount)}; Wiro refuses the id at any other price, and then Wiro support has to finish the purchase.` : '');
554
+ return `To retry, call manage_agent_billing again with agent_guid "${guid}", action buy_credits, pack "${quote?.pack ?? ''}", request_id "${requestId}"${amount !== null ? `, confirm_charge_usd ${amount}` : ''} and confirm: true. That request id never charges twice, and repeating it completes this same purchase. Do not get a new quote for this purchase: a new request id is a new purchase.`
555
+ + (amount !== null ? ` This request id works only while the pack costs ${formatUsd(amount)}. If the price changes, do not buy again: check get_my_agent and the wallet balance with get_account, and if the balance changed, contact Wiro support.` : '');
428
556
  }
429
557
  /**
430
558
  * Whether the agent's packs show an active one of this size bought since
@@ -447,9 +575,8 @@ async function freshPackShows(client, guid, credits, sentAtMs, signal) {
447
575
  }
448
576
  }
449
577
  /**
450
- * After a charging request whose outcome is open, reads the agent again and
451
- * says what it shows: the charge may have gone through, and the agent's
452
- * detail is where its result shows.
578
+ * After a paid request whose answer did not confirm it, reads the agent again
579
+ * and says what it shows.
453
580
  */
454
581
  async function observeOpenOutcome(client, action, before, quote, requestId, sentAtMs, signal) {
455
582
  let row;
@@ -457,11 +584,10 @@ async function observeOpenOutcome(client, action, before, quote, requestId, sent
457
584
  row = await readAgentDetail(client, before.guid, signal);
458
585
  }
459
586
  catch {
460
- return { happened: false, text: 'The agent could not be read afterwards to check it.' };
587
+ return { happened: false, text: 'The agent could not be read afterwards to check it. Check get_my_agent and the wallet balance with get_account first.' };
461
588
  }
462
589
  const after = toAgentDetail(row);
463
590
  const charged = chargeOf(quote);
464
- const restart = waitAction(before.guid, 'Wait until the agent is running again.');
465
591
  switch (action) {
466
592
  case 'subscribe':
467
593
  return after.subscription?.status === 'active'
@@ -470,9 +596,9 @@ async function observeOpenOutcome(client, action, before, quote, requestId, sent
470
596
  row,
471
597
  text: 'The agent now has an active plan, so the payment went through. Do not subscribe again.',
472
598
  result: { chargedUsd: charged, ...(quote?.tier ? { tier: quote.tier } : {}) },
473
- nextAction: waitAction(before.guid, 'Wait until the agent is running.'),
599
+ nextAction: subscribeWait(before),
474
600
  }
475
- : { happened: false, text: 'The agent shows no active plan yet. Check get_my_agent again in a minute before trying again.' };
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.' };
476
602
  case 'renew':
477
603
  return after.subscription?.status === 'active'
478
604
  ? {
@@ -480,9 +606,9 @@ async function observeOpenOutcome(client, action, before, quote, requestId, sent
480
606
  row,
481
607
  text: 'The plan is active again, so the renewal went through. Do not renew it again.',
482
608
  result: { chargedUsd: charged, renewal: 'renewed' },
483
- nextAction: restart,
609
+ nextAction: renewWait(before),
484
610
  }
485
- : { happened: false, text: 'The plan still shows as expired, but the wallet may have been charged anyway. Compare the wallet balance with get_account before renewing again.' };
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.' };
486
612
  case 'upgrade_to_pro': {
487
613
  if (after.tier === 'pro') {
488
614
  return {
@@ -490,7 +616,7 @@ async function observeOpenOutcome(client, action, before, quote, requestId, sent
490
616
  row,
491
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)}.`,
492
618
  result: { chargedUsd: null, tier: 'pro' },
493
- nextAction: restart,
619
+ nextAction: waitIfStarting(before),
494
620
  };
495
621
  }
496
622
  const proPrice = before.pricing?.pro.priceUsd;
@@ -499,8 +625,8 @@ async function observeOpenOutcome(client, action, before, quote, requestId, sent
499
625
  return {
500
626
  happened: false,
501
627
  text: priced
502
- ? 'The plan already shows the Pro price but not the Pro tier, so the upgrade was charged and did not finish. Do not upgrade again; ask Wiro support to finish it.'
503
- : 'The agent still shows Starter, but the wallet may have been charged anyway. Compare the wallet balance with get_account before trying again.',
628
+ ? 'The agent does not show Pro yet. Do not upgrade again: check get_my_agent and the wallet balance with get_account, and if the balance changed, contact Wiro support.'
629
+ : 'The agent still shows Starter. Check get_my_agent and the wallet balance with get_account before trying again; if the balance changed, contact Wiro support.',
504
630
  };
505
631
  }
506
632
  case 'buy_credits': {
@@ -513,18 +639,18 @@ async function observeOpenOutcome(client, action, before, quote, requestId, sent
513
639
  row,
514
640
  text: `The agent's extra credits rose by ${formatCredits(rose)}, so the purchase went through. Do not buy it again.`,
515
641
  result: bought,
516
- nextAction: restart,
642
+ nextAction: waitIfStarting(before),
517
643
  };
518
644
  }
519
645
  const fresh = await freshPackShows(client, before.guid, credits, sentAtMs, signal);
520
646
  if (fresh) {
521
- // The pack is paid for, but its credits are counted only when the
522
- // purchase finishes, which the same request id does without a charge.
647
+ // The pack shows on the agent. If its credits do not show yet, the same
648
+ // request id completes the purchase without a second charge.
523
649
  return {
524
650
  happened: true,
525
651
  row,
526
- text: 'A new pack of that size shows on the agent, so the purchase went through and the wallet was charged. Do not buy it again. '
527
- + `Its credits may not be counted yet. If get_my_agent still does not show them in a few minutes, repeating this purchase with the same request_id "${requestId ?? ''}" and confirm_charge_usd ${charged ?? ''} finishes it without a second charge.`,
652
+ text: 'A new pack of that size shows on the agent, so the purchase went through. Do not buy it again. '
653
+ + `If get_my_agent does not show its credits in a few minutes, repeat this purchase with the same request_id "${requestId ?? ''}" and confirm_charge_usd ${charged ?? ''}; that request id never charges twice.`,
528
654
  result: bought,
529
655
  nextAction: waitAction(before.guid, 'Check that the pack\'s credits show on the agent.'),
530
656
  };
@@ -538,13 +664,20 @@ async function observeOpenOutcome(client, action, before, quote, requestId, sent
538
664
  return { happened: false, text: '' };
539
665
  }
540
666
  }
541
- 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) {
542
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}`);
543
676
  if (/insufficient wallet balance/i.test(text)) {
544
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.`);
545
678
  }
546
679
  if (/purchase request id was already used/i.test(text)) {
547
- return new Error(`${text}\n\nNothing was charged now. This request id belongs to an earlier purchase of another pack or at another price. For a new purchase, get a new quote without request_id. If an earlier call with this id said the wallet was charged but the purchase is still pending, do not buy again: that purchase can only be finished at its original price, so ask Wiro support to finish it.`);
680
+ return new Error(`${text}\n\nNothing was charged now. This request id belongs to an earlier purchase of another pack or at another price. For a new purchase, get a new quote without request_id. If the earlier purchase with this id ended without a clear result, do not buy again: check get_my_agent and the wallet balance with get_account, and if the balance changed, contact Wiro support.`);
548
681
  }
549
682
  const hint = workspaceHint(text);
550
683
  if (hint)
@@ -557,17 +690,23 @@ export function registerManageAgentBilling(server, client) {
557
690
  description: 'Pay for, renew, upgrade or cancel a deployed agent\'s plan, or buy extra credits. Charges '
558
691
  + 'come from the Wiro wallet of the agent\'s workspace (the team wallet for a team agent); the connected '
559
692
  + 'project must belong to that same workspace. Every call without confirm: true only returns what will happen '
560
- + '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 '
561
695
  + 'their approval, then repeat the call with confirm: true and confirm_charge_usd set to the quoted '
562
- + '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 '
563
697
  + 'new quote comes back. Never repeat a confirmed charge that failed or timed out without checking '
564
698
  + 'get_my_agent first. '
565
699
  + 'subscribe starts a plan for an agent without one; renew undoes a cancellation or renews an expired '
566
700
  + 'wallet plan; upgrade_to_pro charges the prorated difference now; buy_credits adds a credit pack '
567
- + '(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 '
568
703
  + 'agent whose plan is billed by card is upgraded and topped up in the Wiro dashboard, and a personal '
569
- + 'card plan is cancelled there; the tool returns the link and charges nothing.',
570
- inputSchema: {
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 '
707
+ + 'admin, the organization\'s owner or the person who deployed the agent can pay for it or change its plan; '
708
+ + 'other members are refused and nothing is charged.',
709
+ inputSchema: strictInput({
571
710
  agent_guid: z.string().min(1).max(100)
572
711
  .describe('The deployed agent\'s guid, from list_my_agents or deploy_agent.'),
573
712
  action: z.enum(ACTIONS)
@@ -582,7 +721,7 @@ export function registerManageAgentBilling(server, client) {
582
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.'),
583
722
  confirm: z.boolean().default(false)
584
723
  .describe('false returns only the quote. true performs the action. Set it only after the user approved it.'),
585
- },
724
+ }),
586
725
  outputSchema: {
587
726
  action: z.enum(ACTIONS),
588
727
  agentGuid: z.string(),
@@ -608,10 +747,12 @@ export function registerManageAgentBilling(server, client) {
608
747
  let sentAtMs = 0;
609
748
  let before;
610
749
  let plan;
750
+ let plainMember = false;
611
751
  const respond = (payload) => {
612
752
  const summary = toAgentSummary(payload.agentRow);
753
+ const summaryLines = formatAgentSummaryLines(summary, plainMember);
613
754
  const dashboardUrl = before?.dashboardUrl ?? agentDashboardUrl(guid);
614
- const heading = {
755
+ const heading = payload.heading ?? {
615
756
  quote: 'Quote (nothing done yet)',
616
757
  done: 'Done',
617
758
  not_needed: 'Nothing to do',
@@ -625,7 +766,7 @@ export function registerManageAgentBilling(server, client) {
625
766
  '',
626
767
  payload.message,
627
768
  '',
628
- ...formatAgentSummaryLines(summary),
769
+ ...(payload.agentStale ? summaryLines.slice(0, 1) : summaryLines),
629
770
  `Dashboard: ${dashboardUrl}`,
630
771
  ].join('\n'),
631
772
  }],
@@ -648,10 +789,22 @@ export function registerManageAgentBilling(server, client) {
648
789
  const row = await readAgentDetail(client, guid, extra.signal);
649
790
  const agent = toAgentDetail(row);
650
791
  before = agent;
792
+ plainMember = await isPlainTeamMember(client, row, extra.signal);
651
793
  const raw = isPlainObject(row.subscription) ? row.subscription : null;
652
- plan = planFor(client, action, agent, raw, { tier, pack, requestId: requestId ?? '' });
794
+ plan = await planFor(client, action, agent, raw, { tier, pack, requestId: requestId ?? '' }, extra.signal);
653
795
  if (plan.outcome !== 'quote' || !plan.perform) {
654
- 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 });
797
+ }
798
+ // A team member without admin rights can still be the person who
799
+ // deployed the agent, who may pay for it, so Wiro is asked before a quote.
800
+ if (plainMember && await teamAgentBillingRefused(client, guid, extra.signal)) {
801
+ return respond({
802
+ outcome: 'refused',
803
+ message: 'Only a team admin, the organization\'s owner or the person who deployed this agent can pay for '
804
+ + 'or change its plan, and this connection acts as a team member without those rights. Nothing was '
805
+ + 'quoted or charged. Ask a team admin or the person who deployed the agent to do it.',
806
+ agentRow: row,
807
+ });
655
808
  }
656
809
  const problem = confirm ? approvalProblem(action, plan.quote, confirm_charge_usd) : null;
657
810
  if (!confirm || problem) {
@@ -692,9 +845,9 @@ export function registerManageAgentBilling(server, client) {
692
845
  // The action went through; the earlier snapshot still identifies the agent.
693
846
  }
694
847
  // A request id that already completed a purchase is answered with
695
- // success again, without a charge and without new credits. A purchase
696
- // also retires any pack past its end date, so the extra credits can
697
- // grow by less than the pack, and the agent's packs decide.
848
+ // success again, without a new charge or new credits. An older pack can
849
+ // end at the same moment, so the extra credits can grow by less than
850
+ // the pack, and the agent's packs decide.
698
851
  const credits = plan.quote?.creditsAdded ?? 0;
699
852
  const extraBefore = toNumber(row.extracredits);
700
853
  const extraAfter = afterRead ? toNumber(after.extracredits) : null;
@@ -709,6 +862,20 @@ export function registerManageAgentBilling(server, client) {
709
862
  agentRow: after,
710
863
  });
711
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
+ }
712
879
  return respond({
713
880
  outcome: 'done',
714
881
  message: fresh
@@ -720,13 +887,18 @@ export function registerManageAgentBilling(server, client) {
720
887
  agentRow: after,
721
888
  });
722
889
  }
890
+ // Without the read-back, the row shows the agent as it was before the change.
891
+ const stale = !afterRead && performed.outcome === 'done';
723
892
  return respond({
724
893
  outcome: performed.outcome,
725
- message: performed.message,
894
+ message: stale
895
+ ? `${performed.message} The agent could not be read afterwards; get_my_agent shows its new status, plan and credits.`
896
+ : performed.message,
726
897
  quote: plan.quote,
727
898
  result: performed.result,
728
899
  nextAction: performed.nextAction,
729
900
  agentRow: after,
901
+ agentStale: stale,
730
902
  });
731
903
  }
732
904
  catch (error) {
@@ -743,7 +915,7 @@ export function registerManageAgentBilling(server, client) {
743
915
  if (observed.happened && observed.row) {
744
916
  return respond({
745
917
  outcome: 'done',
746
- message: `Wiro answered with an error (${text}) after the request was sent, but reading the agent again shows that it went through. ${observed.text}`,
918
+ message: `The answer did not confirm this request, so the agent was read again. ${observed.text}`,
747
919
  quote,
748
920
  result: observed.result,
749
921
  nextAction: observed.nextAction,
@@ -753,10 +925,10 @@ export function registerManageAgentBilling(server, client) {
753
925
  const retry = action === 'buy_credits' && requestId
754
926
  ? retryPurchaseText(guid, quote, requestId)
755
927
  : 'Do not repeat the call until this is checked.';
756
- return errorResult(new Error(`${text}\n\nThe request was sent and the answer does not show whether it went through, so the wallet may have been charged. ${observed.text} ${retry}`));
928
+ return errorResult(new Error(`${text}\n\nThe request was sent and the answer does not show whether it went through. ${observed.text} ${retry}`));
757
929
  }
758
930
  if (error instanceof ApiResultError)
759
- return errorResult(annotateKnownRefusal(error));
931
+ return errorResult(annotateKnownRefusal(error, sentAtMs > 0 ? requestId : undefined));
760
932
  return errorResult(error);
761
933
  }
762
934
  });