@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,11 +1,10 @@
1
1
  import { z } from 'zod';
2
2
  import { WiroApiError } from '../client.js';
3
- import { ApiResultError, asString, isPlainObject, requireApiSuccess, toNumber, truncate, unixToIso, } from './api.js';
3
+ import { AGENT_NOT_FOUND_TEXT, ApiResultError, asString, isPlainObject, requireApiSuccess, toNumber, truncate, unixToIso, } from './api.js';
4
4
  import { pollUntil, reportProgress } from './poll.js';
5
- // Deployed agents and the agent catalog. Agent rows come back with the
6
- // owner's stored credential values, billing links and account ids, none of
7
- // which an assistant should pass on, so every row is rebuilt here from an
8
- // allowlist instead of being passed through.
5
+ import { quotedSpan } from './quoted-text.js';
6
+ // Deployed agents and the agent catalog. Results are rebuilt from an
7
+ // allowlist: only states, labels, prices and settings are returned.
9
8
  export const AGENT_DASHBOARD_URL = 'https://wiro.ai/panel/agents';
10
9
  export function agentDashboardUrl(guid) {
11
10
  return `${AGENT_DASHBOARD_URL}/${encodeURIComponent(guid)}`;
@@ -60,8 +59,24 @@ export function describeAgentStatus(status) {
60
59
  ? STATUS_TEXT[label]
61
60
  : `${STATUS_TEXT[label]} (${status})`;
62
61
  }
63
- export function agentStatusHint(status) {
64
- return STATUS_HINT[agentStatusLabel(status)];
62
+ // On a team agent a team admin or the person who deployed it starts, changes
63
+ // and pays for it (the organization's owner can also pay for it). Other members
64
+ // can read it and message it, so their hints say who can act instead of naming
65
+ // a call they cannot make.
66
+ const TEAM_AGENT_MANAGERS = 'a team admin or the person who deployed it';
67
+ // Connected accounts are the exception: Wiro lets only the person who
68
+ // deployed the agent connect them (connect_agent_oauth).
69
+ export const MEMBER_SETUP_HELP = `On this team agent, ${TEAM_AGENT_MANAGERS} saves credentials and pays for the plan. `
70
+ + 'Only the person who deployed it can connect accounts, such as Instagram or Google Ads.';
71
+ const MEMBER_STATUS_HINT = {
72
+ stopped: `It is not running. On this team agent, ${TEAM_AGENT_MANAGERS} can start it.`,
73
+ error: `The start failed or the agent stopped unexpectedly. On this team agent, ${TEAM_AGENT_MANAGERS} can start it again and read its logs.`,
74
+ setup_required: `${STATUS_HINT.setup_required} ${MEMBER_SETUP_HELP}`,
75
+ };
76
+ /** plainMember: the connection acts as a member of the agent's team without admin rights. */
77
+ export function agentStatusHint(status, plainMember = false) {
78
+ const label = agentStatusLabel(status);
79
+ return (plainMember ? MEMBER_STATUS_HINT[label] : undefined) ?? STATUS_HINT[label];
65
80
  }
66
81
  /** Stopping, queued and starting move on by themselves; every other status stays until someone acts. */
67
82
  export function isTransitionalStatus(status) {
@@ -122,24 +137,47 @@ export const subscriptionSchema = z.object({
122
137
  status: z.string(),
123
138
  billing: z.enum(['wallet', 'card']),
124
139
  switchingToWallet: z.boolean(),
125
- amountUsd: z.number().nullable(),
140
+ amountUsd: z.number().nullable()
141
+ .describe('What the plan costs every 30 days. For a plan that has ended, the agent\'s monthly price, which is what a renew charges.'),
126
142
  periodEndsAt: z.string().optional(),
127
143
  daysRemaining: z.number().nullable(),
128
144
  cancelAtPeriodEnd: z.boolean(),
145
+ atPeriodEnd: z.enum(['renews', 'ends', 'unknown']).optional()
146
+ .describe('For an active plan, what happens at periodEndsAt: it renews, it ends, or unknown for a card plan whose renewal is managed in the Wiro dashboard.'),
129
147
  });
130
- export function toSubscription(value, cancelAtPeriodEnd) {
148
+ /**
149
+ * Whether a plan ends with its current period: true when it ends, false when
150
+ * it renews, null when that is not known. A wallet plan says so itself, and a
151
+ * card plan moving to wallet billing renews from the wallet, even though its
152
+ * card subscription is set to end with the period. Any other card plan is
153
+ * known only when the row carries its card state (cardEnds).
154
+ */
155
+ export function planEndsWithPeriod(subscription, cardEnds) {
156
+ if (subscription.pendingdowngrade === 'cancel')
157
+ return true;
158
+ if (subscription.provider === 'prepaid' || subscription.nextprovider === 'prepaid')
159
+ return false;
160
+ if (cardEnds === true)
161
+ return true;
162
+ return cardEnds === false ? false : null;
163
+ }
164
+ export function toSubscription(value, cardEnds) {
131
165
  if (!isPlainObject(value))
132
166
  return null;
133
167
  const provider = asString(value.provider) ?? 'stripe';
134
168
  const periodEndsAt = unixToIso(value.currentperiodend);
169
+ const status = asString(value.status) ?? 'expired';
170
+ const ends = planEndsWithPeriod(value, cardEnds);
135
171
  return {
136
- status: asString(value.status) ?? 'expired',
172
+ status,
137
173
  billing: provider === 'prepaid' ? 'wallet' : 'card',
138
174
  switchingToWallet: provider !== 'prepaid' && value.nextprovider === 'prepaid',
139
175
  amountUsd: toNumber(value.amount),
140
176
  ...(periodEndsAt ? { periodEndsAt } : {}),
141
177
  daysRemaining: toNumber(value.daysremaining),
142
- cancelAtPeriodEnd: value.pendingdowngrade === 'cancel' || cancelAtPeriodEnd === true,
178
+ // True only when the plan is known to end; atPeriodEnd also tells a plan that renews from an unknown one.
179
+ cancelAtPeriodEnd: ends === true,
180
+ ...(status === 'active' ? { atPeriodEnd: ends === true ? 'ends' : ends === false ? 'renews' : 'unknown' } : {}),
143
181
  };
144
182
  }
145
183
  /** How a new charge for this plan is paid right now. */
@@ -151,7 +189,10 @@ export function chargeRoute(subscription) {
151
189
  return 'card';
152
190
  }
153
191
  export const creditsSchema = z.object({
154
- monthly: z.number(),
192
+ monthly: z.number()
193
+ .describe('The monthly credits this period grants. A plan change inside a period moves it for the time left, so it can differ from renewal.'),
194
+ renewal: z.number().optional()
195
+ .describe('The monthly credits each renewal grants.'),
155
196
  extra: z.number(),
156
197
  used: z.number(),
157
198
  remaining: z.number(),
@@ -159,14 +200,22 @@ export const creditsSchema = z.object({
159
200
  });
160
201
  export function toCredits(row) {
161
202
  const period = asString(row.creditperiod);
203
+ const renewal = toNumber(row.renewalcredits);
162
204
  return {
163
205
  monthly: toNumber(row.monthlycredits) ?? 0,
206
+ ...(renewal !== null ? { renewal } : {}),
164
207
  extra: toNumber(row.extracredits) ?? 0,
165
208
  used: toNumber(row.usedcredits) ?? 0,
166
209
  remaining: toNumber(row.remainingcredits) ?? 0,
167
210
  ...(period ? { period } : {}),
168
211
  };
169
212
  }
213
+ /** The monthly credits as said in a line: those of this period, and those of each renewal when they differ. */
214
+ export function monthlyCreditsText(credits) {
215
+ return credits.renewal !== undefined && credits.renewal !== credits.monthly
216
+ ? `${formatCredits(credits.monthly)} credits this period, ${formatCredits(credits.renewal)} from each renewal`
217
+ : `monthly ${formatCredits(credits.monthly)}`;
218
+ }
170
219
  // ---------------------------------------------------------------------------
171
220
  // Text helpers
172
221
  // ---------------------------------------------------------------------------
@@ -229,7 +278,7 @@ export const credentialStatusSchema = z.object({
229
278
  filled: z.boolean(),
230
279
  requiredMissing: z.boolean(),
231
280
  });
232
- /** Per-credential state only. Field values never leave this function. */
281
+ /** The state of each credential: whether it is filled, connected or missing. */
233
282
  export function summarizeCredentials(credentials, skipKeys = new Set()) {
234
283
  if (!isPlainObject(credentials))
235
284
  return [];
@@ -273,10 +322,14 @@ export function summarizeChannels(value) {
273
322
  };
274
323
  });
275
324
  }
325
+ /** The scheduled task a custom build starts with. It runs only once its text has been written. */
326
+ export const STARTER_TASK_KEY = 'cs-cron-my-scheduled-task';
276
327
  export const agentTaskSchema = z.object({
277
328
  key: z.string(),
278
329
  description: z.string().optional(),
279
330
  enabled: z.boolean(),
331
+ notWritten: z.boolean().optional()
332
+ .describe('The starter task whose text was never written. It does not run, even when it shows on, until its text is saved with manage_agent_custom_skill.'),
280
333
  interval: z.string().nullable().optional(),
281
334
  source: z.enum(['template', 'skill', 'user']).optional(),
282
335
  instructions: z.string().optional(),
@@ -301,6 +354,7 @@ export function summarizeTasks(value, maxInstructionLength = 300) {
301
354
  key: asString(task.key) ?? '',
302
355
  ...(description ? { description } : {}),
303
356
  enabled: task.enabled !== false,
357
+ ...(asString(task.key) === STARTER_TASK_KEY && task._edited === false ? { notWritten: true } : {}),
304
358
  ...(interval !== undefined ? { interval: interval || null } : {}),
305
359
  ...(source ? { source } : {}),
306
360
  ...(shortened ? { instructions: shortened } : {}),
@@ -339,7 +393,8 @@ export const agentSummarySchema = z.object({
339
393
  title: z.string(),
340
394
  status: z.number().int().nullable(),
341
395
  statusLabel: agentStatusLabelSchema,
342
- setupRequired: z.boolean(),
396
+ setupRequired: z.boolean()
397
+ .describe('Whether the agent waits for setup. list_my_agents goes by the status (6). get_my_agent also checks each required credential and names what is missing.'),
343
398
  workspace: z.enum(['personal', 'team']),
344
399
  template: agentTemplateRefSchema,
345
400
  tier: z.string().optional(),
@@ -351,13 +406,16 @@ export const agentSummarySchema = z.object({
351
406
  skills: z.array(z.string()),
352
407
  createdAt: z.string().optional(),
353
408
  updatedAt: z.string().optional(),
409
+ lastActivityAt: z.string().optional()
410
+ .describe('When the agent\'s newest message was written, on any channel, scheduled runs included. Left out when it has none.'),
354
411
  });
355
412
  export const agentDetailSchema = agentSummarySchema.extend({
356
413
  description: z.string().optional(),
357
414
  cover: z.string().optional(),
358
415
  categories: z.array(z.string()),
359
416
  pricing: tierPricingSchema.nullable(),
360
- extraCreditPacks: z.array(extraCreditPackSchema),
417
+ extraCreditPacks: z.array(extraCreditPackSchema)
418
+ .describe('The extra credit packs this plan can buy. Only an active Pro plan can buy them, so any other plan lists none.'),
361
419
  models: modelSettingsSchema.optional(),
362
420
  selectableModels: z.array(selectableModelSchema).optional(),
363
421
  enabledCommands: z.array(z.string()).nullable(),
@@ -406,6 +464,15 @@ function timestamps(row, map) {
406
464
  }
407
465
  return out;
408
466
  }
467
+ /**
468
+ * The plan as it is shown. A plan that has ended renews at the agent's monthly
469
+ * price, not at the amount of its last period, so that is its one price.
470
+ */
471
+ function planOf(row) {
472
+ const plan = toSubscription(row.subscription, row.stripecancelatperiodend);
473
+ const price = toNumber(row.monthlypriceusd);
474
+ return plan && plan.status !== 'active' && price !== null && price > 0 ? { ...plan, amountUsd: price } : plan;
475
+ }
409
476
  /** The compact row used by list_my_agents. */
410
477
  export function toAgentSummary(row) {
411
478
  const status = readAgentStatus(row);
@@ -423,11 +490,11 @@ export function toAgentSummary(row) {
423
490
  ...(tier ? { tier } : {}),
424
491
  monthlyPriceUsd: toNumber(row.monthlypriceusd),
425
492
  credits: toCredits(row),
426
- subscription: toSubscription(row.subscription, row.stripecancelatperiodend),
493
+ subscription: planOf(row),
427
494
  ...(timezone !== undefined ? { timezone } : {}),
428
495
  ...(sessionMode ? { teamSessionMode: sessionMode } : {}),
429
496
  skills: toStringArray(row.skills ?? row.enabledSkills),
430
- ...timestamps(row, { createdAt: 'createdat', updatedAt: 'updatedat' }),
497
+ ...timestamps(row, { createdAt: 'createdat', updatedAt: 'updatedat', lastActivityAt: 'lastactivityat' }),
431
498
  };
432
499
  }
433
500
  /** The models an agent can switch to, with their credits per 1M input and output tokens when Wiro lists them. */
@@ -454,29 +521,54 @@ export function selectableModelSlugs(row) {
454
521
  return new Set(selectableModels(row.tokenRates).map(model => model.slug));
455
522
  }
456
523
  /**
457
- * The full sanitized view of one deployed agent (Detail, Deploy and Update
458
- * rows). Credential values, billing session links, account ids and the
459
- * team id are dropped; only states, labels and settings remain.
524
+ * The keys of the credentials an agent's setup waits for: a credential with a
525
+ * required field empty, a required credential that holds no value yet, or a
526
+ * required sign-in that is not connected. A custom build's setup waits only for
527
+ * the main credential of each active skill, which skillsmeta lists. Its chat
528
+ * channels are never among them.
529
+ */
530
+ export function credentialKeysSetupWaitsFor(row) {
531
+ const channelKeys = new Set(summarizeChannels(row.communicationChannels).map(channel => channel.credentialKey));
532
+ const activeKeys = templateRef(row).custom && isPlainObject(row.skillsmeta)
533
+ ? new Set(Object.values(row.skillsmeta).filter(isPlainObject)
534
+ .filter(meta => meta.requires_credentials === true)
535
+ .map(meta => asString(meta.credential_key))
536
+ .filter((key) => Boolean(key)))
537
+ : null;
538
+ return new Set(summarizeCredentials(row.credentials, channelKeys)
539
+ // A required credential whose fields are all optional has no empty required
540
+ // field to show, and the setup still waits until it holds a value.
541
+ .filter(credential => credential.requiredMissing
542
+ || (!credential.optional && !credential.connected && (credential.oauth || (row.setuprequired === true && !credential.filled))))
543
+ .filter(credential => !activeKeys || activeKeys.has(credential.key))
544
+ // A group with no optional flag has no template row and no skill behind it, only saved values left over.
545
+ .filter((credential) => {
546
+ const group = isPlainObject(row.credentials) ? row.credentials[credential.key] : undefined;
547
+ return isPlainObject(group) && typeof group.optional === 'boolean';
548
+ })
549
+ .map(credential => credential.key));
550
+ }
551
+ /**
552
+ * The full view of one deployed agent (Detail, Deploy and Update rows): its
553
+ * status, plan, prices, credits and settings, and the state of each
554
+ * credential and channel.
460
555
  */
461
556
  export function toAgentDetail(row) {
462
557
  const summary = toAgentSummary(row);
463
558
  const agent = isPlainObject(row.agent) ? row.agent : {};
464
559
  const channels = summarizeChannels(row.communicationChannels);
465
560
  const channelKeys = new Set(channels.map(channel => channel.credentialKey));
466
- const credentials = summarizeCredentials(row.credentials, channelKeys);
561
+ // A credential is required and missing exactly when the setup waits for it, so its row and the setup line agree.
562
+ const setupWaitsFor = credentialKeysSetupWaitsFor(row);
563
+ const credentials = summarizeCredentials(row.credentials, channelKeys)
564
+ .map(credential => ({ ...credential, requiredMissing: setupWaitsFor.has(credential.key) }));
467
565
  const description = asString(row.description);
468
566
  const cover = asString(row.cover) || asString(agent.cover);
469
567
  const agentModel = isPlainObject(row.agentModel) ? row.agentModel : undefined;
470
568
  const models = selectableModels(row.tokenRates);
471
- // A custom build's setup waits only for the main credential of each active
472
- // skill, which skillsmeta lists, so only those count as missing.
473
- const activeKeys = summary.template.custom && isPlainObject(row.skillsmeta)
474
- ? new Set(Object.values(row.skillsmeta).filter(isPlainObject)
475
- .filter(meta => meta.requires_credentials === true)
476
- .map(meta => asString(meta.credential_key))
477
- .filter((key) => Boolean(key)))
478
- : null;
479
- const packs = Array.isArray(agent.extracreditpacks)
569
+ // Packs are bought only on an active Pro plan, so only that plan lists them.
570
+ const canBuyPacks = summary.tier === 'pro' && summary.subscription?.status === 'active';
571
+ const packs = canBuyPacks && Array.isArray(agent.extracreditpacks)
480
572
  ? agent.extracreditpacks.filter(isPlainObject).filter(pack => pack.enabled !== false)
481
573
  : [];
482
574
  return {
@@ -504,11 +596,8 @@ export function toAgentDetail(row) {
504
596
  ...(models.length > 0 ? { selectableModels: models } : {}),
505
597
  enabledCommands: Array.isArray(row.enabledCommands) ? toStringArray(row.enabledCommands) : null,
506
598
  credentials,
507
- // A required sign-in that is not connected also holds back the setup, so it
508
- // is listed as missing too.
509
599
  missingCredentials: credentials
510
- .filter(credential => credential.requiredMissing || (credential.oauth && !credential.optional && !credential.connected))
511
- .filter(credential => !activeKeys || activeKeys.has(credential.key))
600
+ .filter(credential => credential.requiredMissing)
512
601
  .map(({ key, title, oauth }) => ({ key, title, oauth })),
513
602
  channels,
514
603
  scheduledTasks: summarizeTasks(row.scheduledskills),
@@ -523,41 +612,94 @@ export function toAgentDetail(row) {
523
612
  dashboardUrl: agentDashboardUrl(summary.guid),
524
613
  };
525
614
  }
526
- export function formatSubscriptionLine(subscription) {
527
- if (!subscription)
528
- return 'No plan. manage_agent_billing with action subscribe starts one.';
615
+ export function formatSubscriptionLine(subscription, plainMember = false) {
616
+ if (!subscription) {
617
+ return plainMember
618
+ ? 'No plan. On this team agent, a team admin, the organization\'s owner or the person who deployed it can start one.'
619
+ : 'No plan. manage_agent_billing with action subscribe starts one.';
620
+ }
529
621
  const parts = [`${subscription.status}, billed ${subscription.billing === 'wallet' ? 'from the wallet' : 'by card'}`];
530
- if (subscription.amountUsd !== null)
531
- parts.push(`${formatUsd(subscription.amountUsd)} per 30 days`);
622
+ if (subscription.amountUsd !== null) {
623
+ parts.push(subscription.status === 'expired' && subscription.billing === 'wallet'
624
+ ? `a renew charges ${formatUsd(subscription.amountUsd)} per 30 days`
625
+ : `${formatUsd(subscription.amountUsd)} per 30 days`);
626
+ }
532
627
  if (subscription.periodEndsAt) {
628
+ const atPeriodEnd = subscription.atPeriodEnd ?? (subscription.cancelAtPeriodEnd ? 'ends' : 'renews');
533
629
  if (subscription.status !== 'active') {
534
630
  parts.push(`period ended ${subscription.periodEndsAt}`);
535
631
  }
632
+ else if (atPeriodEnd === 'ends') {
633
+ parts.push(`cancelled, ends after ${subscription.periodEndsAt}`);
634
+ }
635
+ else if (atPeriodEnd === 'renews') {
636
+ parts.push(`renews after ${subscription.periodEndsAt}`);
637
+ }
536
638
  else {
537
- parts.push(subscription.cancelAtPeriodEnd
538
- ? `cancelled, ends after ${subscription.periodEndsAt}`
539
- : `renews after ${subscription.periodEndsAt}`);
639
+ // Whether this card plan renews is not known here.
640
+ parts.push(`paid through ${subscription.periodEndsAt}`);
540
641
  }
541
642
  }
542
643
  if (subscription.switchingToWallet)
543
644
  parts.push('switching to wallet billing at period end');
544
645
  return parts.join(', ');
545
646
  }
546
- export function formatAgentSummaryLines(agent) {
647
+ /** Without an active plan, an agent starts only on the extra credits it has left. */
648
+ export function startNeedsPlan(agent) {
649
+ return agent.subscription?.status !== 'active' && agent.credits.extra <= 0;
650
+ }
651
+ /** renew brings back an expired wallet plan; any other agent without an active plan takes a new one. */
652
+ function planActionForStart(agent) {
653
+ return agent.subscription?.status === 'expired' && agent.subscription.billing === 'wallet' ? 'renew' : 'subscribe';
654
+ }
655
+ function noPlanStartHint(agent) {
656
+ return 'It has no active plan and no extra credits left, so it cannot start. '
657
+ + (planActionForStart(agent) === 'renew'
658
+ ? 'Its wallet plan has expired; manage_agent_billing with action renew restarts it.'
659
+ : 'manage_agent_billing with action subscribe pays for a plan and starts the agent.');
660
+ }
661
+ /** An agent's title inside a line of this server's own text: on one line, in a code span it cannot close. */
662
+ export function agentTitleSpan(title) {
663
+ return title.trim() ? quotedSpan(title) : 'Untitled agent';
664
+ }
665
+ export function formatAgentSummaryLines(agent, plainMember = false) {
547
666
  const template = agent.template.custom
548
667
  ? 'custom build'
549
668
  : `template ${agent.template.title ?? agent.template.slug ?? agent.template.guid}`;
669
+ const hint = !plainMember && (agent.status === 0 || agent.status === 5) && startNeedsPlan(agent)
670
+ ? `${agent.status === 5 ? 'The start failed or the agent stopped unexpectedly.' : 'It is not running.'} ${noPlanStartHint(agent)}`
671
+ : agentStatusHint(agent.status, plainMember);
550
672
  return [
551
- `**Agent:** ${agent.title} (\`${agent.guid}\`), ${template}, ${agent.workspace} workspace`,
552
- `**Status:** ${describeAgentStatus(agent.status)}. ${agentStatusHint(agent.status)}`,
553
- `**Plan:** ${agent.tier ?? 'no tier'}; ${formatSubscriptionLine(agent.subscription)}`,
554
- `**Credits:** ${formatCredits(agent.credits.remaining)} left (monthly ${formatCredits(agent.credits.monthly)}, extra ${formatCredits(agent.credits.extra)}, used ${formatCredits(agent.credits.used)})`,
673
+ `**Agent:** ${agentTitleSpan(agent.title)} (\`${agent.guid}\`), ${template}, ${agent.workspace} workspace`,
674
+ `**Status:** ${describeAgentStatus(agent.status)}. ${hint}`,
675
+ `**Plan:** ${agent.tier ?? 'no tier'}; ${formatSubscriptionLine(agent.subscription, plainMember)}`,
676
+ `**Credits:** ${formatCredits(agent.credits.remaining)} left (${monthlyCreditsText(agent.credits)}, extra ${formatCredits(agent.credits.extra)}, used ${formatCredits(agent.credits.used)})`,
555
677
  ];
556
678
  }
557
- const SHORTENED_NOTE = ' (its text is long; manage_agent_custom_skill history shows it in full)';
679
+ /** Where an entry the list shortens is read in full. */
680
+ function shortenedNote(key) {
681
+ return ` (its text is long; get_my_agent with entry "${key}" shows it in full)`;
682
+ }
683
+ /** A scheduled task as a list line: key, on or off, schedule and description, then shortenedNote if its text was cut. */
684
+ export function formatScheduledTaskLine(task, shortenedNote = '') {
685
+ return `- \`${task.key}\` ${task.enabled ? 'on' : 'off'}${task.notWritten ? ' (its text is not written yet, so it does not run)' : ''}${task.interval ? `, ${task.interval}` : ''}${task.description ? `: ${quotedSpan(task.description)}` : ''}${task.instructionsTruncated ? shortenedNote : ''}`;
686
+ }
687
+ /** A custom instruction as a list line: key, on or off and description, then shortenedNote if its text was cut. */
688
+ export function formatCustomInstructionLine(task, shortenedNote = '') {
689
+ return `- \`${task.key}\` ${task.enabled ? 'on' : 'off'}${task.description ? `: ${quotedSpan(task.description)}` : ''}${task.instructionsTruncated ? shortenedNote : ''}`;
690
+ }
558
691
  function formatRate(credits) {
559
692
  return credits.toLocaleString('en-US', { maximumFractionDigits: 3 });
560
693
  }
694
+ /** Each model with its credits per 1M input and output tokens, separated by semicolons. */
695
+ export function formatModelRates(models) {
696
+ return models.map(model => {
697
+ const name = model.label ? `${model.label} (${model.slug})` : model.slug;
698
+ return model.inputCreditsPer1M !== undefined && model.outputCreditsPer1M !== undefined
699
+ ? `${name} ${formatRate(model.inputCreditsPer1M)} and ${formatRate(model.outputCreditsPer1M)}`
700
+ : `${name} rate unknown`;
701
+ }).join('; ');
702
+ }
561
703
  /** The chat and scheduled-task models, then every model the agent can switch to with its rates. */
562
704
  function formatModelsLine(agent) {
563
705
  const choices = agent.selectableModels ?? [];
@@ -566,22 +708,19 @@ function formatModelsLine(agent) {
566
708
  if (agent.models)
567
709
  parts.push(`chat ${nameOf(agent.models.chat)}, scheduled tasks ${nameOf(agent.models.cron)}.`);
568
710
  if (choices.length > 0) {
569
- const rates = choices.map(model => {
570
- const name = model.label ? `${model.label} (${model.slug})` : model.slug;
571
- return model.inputCreditsPer1M !== undefined && model.outputCreditsPer1M !== undefined
572
- ? `${name} ${formatRate(model.inputCreditsPer1M)} and ${formatRate(model.outputCreditsPer1M)}`
573
- : `${name} rate unknown`;
574
- });
575
- parts.push(`Models you can switch to, in credits per 1M input and output tokens: ${rates.join('; ')}.`);
711
+ parts.push(`Models you can switch to, in credits per 1M input and output tokens: ${formatModelRates(choices)}.`);
576
712
  }
577
713
  return parts.length > 0 ? `**Models:** ${parts.join(' ')}` : null;
578
714
  }
579
- export function formatAgentDetail(agent) {
580
- const lines = formatAgentSummaryLines(agent);
715
+ export function formatAgentDetail(agent, plainMember = false) {
716
+ const lines = formatAgentSummaryLines(agent, plainMember);
717
+ if (agent.lastActivityAt)
718
+ lines.push(`**Last activity:** ${agent.lastActivityAt}`);
581
719
  if (agent.pricing) {
582
- // This agent's own Starter and Pro prices, which can differ from its template's.
720
+ // Today's Starter and Pro prices for this agent's own skills, which can
721
+ // differ from its template's and from the price its plan pays.
583
722
  const { starter, pro } = agent.pricing;
584
- lines.push(`**Prices now:** Starter ${formatUsd(starter.priceUsd)} per 30 days with ${formatCredits(starter.monthlyCredits)} credits per month; Pro ${formatUsd(pro.priceUsd)} per 30 days with ${formatCredits(pro.monthlyCredits)} credits per month. A new plan or an upgrade uses these.`);
723
+ lines.push(`**Prices now:** Starter ${formatUsd(starter.priceUsd)} per 30 days with ${formatCredits(starter.monthlyCredits)} credits per month; Pro ${formatUsd(pro.priceUsd)} per 30 days with ${formatCredits(pro.monthlyCredits)} credits per month. A new plan or an upgrade uses these, and a skill change moves the plan to today's prices for all its skills.`);
585
724
  }
586
725
  const models = formatModelsLine(agent);
587
726
  if (models)
@@ -604,25 +743,23 @@ export function formatAgentDetail(agent) {
604
743
  for (const credential of agent.credentials) {
605
744
  const state = credential.connected
606
745
  ? 'connected'
607
- : credential.filled
608
- ? 'filled'
609
- : credential.requiredMissing
610
- ? 'missing (required)'
746
+ : credential.requiredMissing
747
+ ? (credential.oauth ? 'not connected (required)' : 'missing (required)')
748
+ : credential.filled
749
+ ? 'filled'
611
750
  : 'not set';
612
751
  lines.push(`- ${credential.title} (\`${credential.key}\`): ${state}${credential.optional ? ', optional' : ''}`);
613
752
  }
614
753
  }
615
754
  if (agent.scheduledTasks.length > 0) {
616
755
  lines.push('', '### Scheduled tasks', '');
617
- for (const task of agent.scheduledTasks) {
618
- lines.push(`- \`${task.key}\` ${task.enabled ? 'on' : 'off'}${task.interval ? `, ${task.interval}` : ''}${task.description ? `: ${task.description}` : ''}${task.instructionsTruncated ? SHORTENED_NOTE : ''}`);
619
- }
756
+ for (const task of agent.scheduledTasks)
757
+ lines.push(formatScheduledTaskLine(task, shortenedNote(task.key)));
620
758
  }
621
759
  if (agent.customInstructions.length > 0) {
622
760
  lines.push('', '### Custom instructions', '');
623
- for (const task of agent.customInstructions) {
624
- lines.push(`- \`${task.key}\`${task.description ? `: ${task.description}` : ''}${task.instructionsTruncated ? SHORTENED_NOTE : ''}`);
625
- }
761
+ for (const task of agent.customInstructions)
762
+ lines.push(formatCustomInstructionLine(task, shortenedNote(task.key)));
626
763
  }
627
764
  lines.push('', `Dashboard: ${agent.dashboardUrl}`);
628
765
  return lines.join('\n');
@@ -661,12 +798,12 @@ const GUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{
661
798
  export function isGuid(value) {
662
799
  return GUID_PATTERN.test(value.trim());
663
800
  }
664
- /** The raw Detail row. Callers must pass it through toAgentDetail before returning anything. */
801
+ /** Reads one agent's Detail row; tool results are built from it with toAgentDetail or toAgentSummary. */
665
802
  export async function readAgentDetail(client, guid, signal) {
666
803
  const body = requireApiSuccess(await client.post('/UserAgent/Detail', { guid }, signal), 'The agent could not be read.');
667
804
  const rows = Array.isArray(body.useragents) ? body.useragents.filter(isPlainObject) : [];
668
805
  if (!rows[0])
669
- throw new Error(`No agent was found for agent_guid "${guid}".`);
806
+ throw new Error(AGENT_NOT_FOUND_TEXT);
670
807
  return rows[0];
671
808
  }
672
809
  // Every Detail read prices the agent, so polling stays slower than for tasks.
@@ -685,16 +822,30 @@ export async function waitForAgentToSettle(client, guid, waitSeconds, extra, fal
685
822
  });
686
823
  return {
687
824
  row: result.value ?? fallbackRow,
825
+ fresh: result.value !== undefined,
688
826
  settled: result.done,
689
827
  elapsedMs: result.elapsedMs,
690
828
  };
691
829
  }
830
+ /** The credential that holds the Wiro project an agent runs models with. */
831
+ export const WIRO_PROJECT_CREDENTIAL = 'wiro';
832
+ /** The call that connects a Wiro project to an agent. It shows the project first; lead says why it comes now. */
833
+ export function bindProjectStep(agentGuid, lead) {
834
+ return {
835
+ tool: 'bind_agent_project',
836
+ arguments: { agent_guid: agentGuid },
837
+ reason: `${lead} This shows the project first and changes nothing without confirm: true, and no key is needed.`,
838
+ };
839
+ }
692
840
  /** The call that starts on the first missing credential, with every argument it needs. */
693
841
  export function missingCredentialStep(agent) {
694
842
  const missing = agent.missingCredentials[0];
695
843
  if (!missing)
696
844
  return undefined;
697
845
  const keys = agent.missingCredentials.map(item => item.key).join(', ');
846
+ if (missing.key === WIRO_PROJECT_CREDENTIAL) {
847
+ return bindProjectStep(agent.guid, `Provide the missing credentials (${keys}), starting with ${missing.title}.`);
848
+ }
698
849
  return missing.oauth
699
850
  ? {
700
851
  tool: 'connect_agent_oauth',
@@ -707,8 +858,21 @@ export function missingCredentialStep(agent) {
707
858
  reason: `Provide the missing credentials (${keys}). This lists the ${missing.title} fields to ask the user for.`,
708
859
  };
709
860
  }
710
- /** The most useful next call for an agent in this state, if any. */
711
- export function nextStepForAgent(agent, waitSeconds) {
861
+ /**
862
+ * The most useful next call for an agent in this state, if any. For a team
863
+ * member without admin rights it is only the wait for a status that moves on
864
+ * by itself.
865
+ */
866
+ export function nextStepForAgent(agent, waitSeconds, plainMember = false) {
867
+ const wait = isTransitionalStatus(agent.status)
868
+ ? {
869
+ tool: 'get_my_agent',
870
+ arguments: { agent_guid: agent.guid, wait_seconds: waitSeconds },
871
+ reason: 'Wait until the agent has settled. Do not repeat the action that started it.',
872
+ }
873
+ : undefined;
874
+ if (plainMember)
875
+ return wait;
712
876
  if (agent.status === 6 && agent.subscription?.status !== 'active') {
713
877
  return {
714
878
  tool: 'manage_agent_billing',
@@ -719,11 +883,13 @@ export function nextStepForAgent(agent, waitSeconds) {
719
883
  const missing = agent.missingCredentials[0];
720
884
  if (missing)
721
885
  return missingCredentialStep(agent);
722
- if (isTransitionalStatus(agent.status)) {
886
+ if (wait)
887
+ return wait;
888
+ if ((agent.status === 0 || agent.status === 5) && startNeedsPlan(agent)) {
723
889
  return {
724
- tool: 'get_my_agent',
725
- arguments: { agent_guid: agent.guid, wait_seconds: waitSeconds },
726
- reason: 'Wait until the agent has settled. Do not repeat the action that started it.',
890
+ tool: 'manage_agent_billing',
891
+ arguments: { agent_guid: agent.guid, action: planActionForStart(agent) },
892
+ reason: 'The agent has no active plan and no extra credits left, so it cannot start. This returns the price; nothing is charged without confirm: true.',
727
893
  };
728
894
  }
729
895
  if (agent.status === 0 || agent.status === 5) {
@@ -735,13 +901,18 @@ export function nextStepForAgent(agent, waitSeconds) {
735
901
  }
736
902
  return undefined;
737
903
  }
738
- // Refusals Wiro sends before it touches the wallet, or after rolling back
739
- // everything it wrote. Only these are known to come before any charge, so any
740
- // other result:false answer to a paid call leaves the outcome open.
904
+ // Refusals known to leave the wallet untouched.
741
905
  const PRE_CHARGE_CODES = new Set([95, 96, 97, 99, 101, 102, 103, 104, 105, 106, 107, 108, 109]);
906
+ const WALLET_NOT_READ = /could not retrieve wallet balance/i;
907
+ /** Whether a refusal says the wallet could not be read, which says nothing about what it holds. */
908
+ export function isWalletNotRead(error) {
909
+ return error instanceof ApiResultError && WALLET_NOT_READ.test(error.message);
910
+ }
911
+ /** The words after such a refusal. No funds are asked for: the wallet may hold plenty. */
912
+ export const WALLET_NOT_READ_NOTE = 'Wiro could not read the balance just now. That is not a low balance, and nothing was charged. Try again in a moment; if it keeps saying so, get_account shows the wallet.';
742
913
  const PRE_CHARGE_REFUSALS = [
743
914
  /insufficient wallet balance/i,
744
- /could not retrieve wallet balance/i,
915
+ WALLET_NOT_READ,
745
916
  /wallet deduction failed/i,
746
917
  /subscription (is )?already active/i,
747
918
  /already on (the )?(pro|starter) tier/i,
@@ -758,11 +929,14 @@ const PRE_CHARGE_REFUSALS = [
758
929
  /switch to (the )?(team|personal) context|belongs to a different team/i,
759
930
  /^request parameter \[/i,
760
931
  /mutually exclusive|agent terms acceptance|customskills must be|skill key may only contain/i,
932
+ /^unknown skill: /i,
933
+ /^the tier change could not be saved\./i,
934
+ /^we couldn't (schedule|undo) the cancellation\./i,
761
935
  /enabledchannels is not accepted|credentials must be an object|credential field|is missing required fields/i,
762
936
  /chat mode is not accepted|is managed by (wiro|the connection flow)|^agent-[a-z-]+$/i,
763
937
  /^failed to deploy agent|failed to save communication channel settings|failed to save custom skill/i,
764
938
  ];
765
- /** Whether a result:false answer is one that Wiro sends before any charge. */
939
+ /** Whether a result:false answer is one of the refusals known to leave the wallet untouched. */
766
940
  export function isPreChargeRefusal(error) {
767
941
  if (!(error instanceof ApiResultError))
768
942
  return false;
@@ -770,22 +944,51 @@ export function isPreChargeRefusal(error) {
770
944
  return true;
771
945
  return PRE_CHARGE_REFUSALS.some(pattern => pattern.test(error.message.trim()));
772
946
  }
947
+ const CHARGED_START = 'The wallet was charged ';
948
+ const CHARGED_END = /, but the (tier change|renewal) did not finish\. Please contact support and do not try again\.$/;
949
+ /**
950
+ * Whether a result:false answer says what the wallet paid. Its sentence is the
951
+ * whole outcome: it is shown as it is, and the call is not repeated.
952
+ */
953
+ export function isKnownPaidOutcome(error) {
954
+ if (!(error instanceof ApiResultError))
955
+ return false;
956
+ const message = error.message.trim();
957
+ return message.startsWith(CHARGED_START) && CHARGED_END.test(message);
958
+ }
959
+ /**
960
+ * Whether a result:false answer carries code 110. Its answer is the whole outcome:
961
+ * the change is reported as failed, what the agent and the wallet show is read by
962
+ * hand, and the call is not repeated.
963
+ */
964
+ export function isKnownFailedChange(error) {
965
+ return error instanceof ApiResultError && error.codes.some(code => Number(code) === 110);
966
+ }
773
967
  /**
774
968
  * After a paid request was sent, whether it may still have gone through.
775
- * Only the known refusals and client errors (other than a timeout or rate
776
- * limit) settle it; any other answer, a network failure or a server error
777
- * leaves it open.
969
+ * Only the known refusals, an answer that says what the wallet paid or carries
970
+ * code 110, and client errors (other than a timeout or rate limit) settle it; any
971
+ * other answer, a network failure or a server error leaves it open.
778
972
  */
779
973
  export function outcomeUnknown(error) {
780
974
  if (error instanceof ApiResultError)
781
- return !isPreChargeRefusal(error);
975
+ return !isPreChargeRefusal(error) && !isKnownPaidOutcome(error) && !isKnownFailedChange(error);
782
976
  if (error instanceof WiroApiError) {
783
977
  return error.status >= 500 || error.status === 408 || error.status === 429;
784
978
  }
785
979
  return true;
786
980
  }
787
- /** Advice for a status reached after a start, renew or deploy. */
788
- export function settledStatusAdvice(status) {
981
+ /** Advice for a status reached after a start, renew or deploy. With the agent, a failed agent without a plan or extra credits is sent to billing. */
982
+ export function settledStatusAdvice(status, plainMember = false, agent) {
983
+ if (!plainMember && agent && status === 5 && startNeedsPlan(agent)) {
984
+ return `The agent failed to start. ${noPlanStartHint(agent)}`;
985
+ }
986
+ if (plainMember && status === 5) {
987
+ return `The agent failed to start. On this team agent, ${TEAM_AGENT_MANAGERS} can start it again and read its logs.`;
988
+ }
989
+ if (plainMember && status === 6) {
990
+ return `The agent needs setup: it has no paid plan, or required credentials are missing. ${MEMBER_SETUP_HELP}`;
991
+ }
789
992
  switch (status) {
790
993
  case 4:
791
994
  return 'The agent is running. send_agent_message with agent set to its guid talks to it.';
@@ -796,7 +999,17 @@ export function settledStatusAdvice(status) {
796
999
  case 6:
797
1000
  return 'The agent needs setup: it has no paid plan, or required credentials are missing.';
798
1001
  default:
799
- return agentStatusHint(status);
1002
+ return agentStatusHint(status, plainMember);
800
1003
  }
801
1004
  }
1005
+ /** Advice for a read, which cannot tell whether a start came first: a stopped agent gets what holds now. */
1006
+ export function readStatusAdvice(agent, plainMember = false) {
1007
+ if (agent.status !== 0)
1008
+ return settledStatusAdvice(agent.status, plainMember, agent);
1009
+ if (plainMember)
1010
+ return `The agent is stopped. On this team agent, ${TEAM_AGENT_MANAGERS} can start it.`;
1011
+ return startNeedsPlan(agent)
1012
+ ? `The agent is stopped. ${noPlanStartHint(agent)}`
1013
+ : 'The agent is stopped. control_agent with action start starts it.';
1014
+ }
802
1015
  //# sourceMappingURL=user-agent.js.map