@wiro-ai/wiro-mcp 1.4.1 → 1.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +38 -15
- package/dist/client.d.ts +19 -6
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +44 -27
- package/dist/client.js.map +1 -1
- package/dist/instructions.d.ts.map +1 -1
- package/dist/instructions.js +7 -4
- package/dist/instructions.js.map +1 -1
- package/dist/playbooks.d.ts.map +1 -1
- package/dist/playbooks.js +46 -35
- package/dist/playbooks.js.map +1 -1
- package/dist/prompts.d.ts.map +1 -1
- package/dist/prompts.js +3 -2
- package/dist/prompts.js.map +1 -1
- package/dist/tools/apply-agent-skills.d.ts.map +1 -1
- package/dist/tools/apply-agent-skills.js +458 -109
- package/dist/tools/apply-agent-skills.js.map +1 -1
- package/dist/tools/bind-agent-project.d.ts +4 -0
- package/dist/tools/bind-agent-project.d.ts.map +1 -0
- package/dist/tools/bind-agent-project.js +405 -0
- package/dist/tools/bind-agent-project.js.map +1 -0
- package/dist/tools/cancel-agent-message.d.ts.map +1 -1
- package/dist/tools/cancel-agent-message.js +6 -3
- package/dist/tools/cancel-agent-message.js.map +1 -1
- package/dist/tools/cancel-task.js +3 -3
- package/dist/tools/cancel-task.js.map +1 -1
- package/dist/tools/clone-workflow.d.ts.map +1 -1
- package/dist/tools/clone-workflow.js +5 -4
- package/dist/tools/clone-workflow.js.map +1 -1
- package/dist/tools/connect-agent-oauth.d.ts.map +1 -1
- package/dist/tools/connect-agent-oauth.js +161 -40
- package/dist/tools/connect-agent-oauth.js.map +1 -1
- package/dist/tools/control-agent.d.ts.map +1 -1
- package/dist/tools/control-agent.js +109 -36
- package/dist/tools/control-agent.js.map +1 -1
- package/dist/tools/create-workflow.d.ts +3 -0
- package/dist/tools/create-workflow.d.ts.map +1 -1
- package/dist/tools/create-workflow.js +12 -11
- package/dist/tools/create-workflow.js.map +1 -1
- package/dist/tools/delete-agent-session.d.ts.map +1 -1
- package/dist/tools/delete-agent-session.js +18 -12
- package/dist/tools/delete-agent-session.js.map +1 -1
- package/dist/tools/delete-agent.d.ts.map +1 -1
- package/dist/tools/delete-agent.js +12 -9
- package/dist/tools/delete-agent.js.map +1 -1
- package/dist/tools/delete-workflow.d.ts.map +1 -1
- package/dist/tools/delete-workflow.js +5 -4
- package/dist/tools/delete-workflow.js.map +1 -1
- package/dist/tools/deploy-agent.d.ts.map +1 -1
- package/dist/tools/deploy-agent.js +239 -46
- package/dist/tools/deploy-agent.js.map +1 -1
- package/dist/tools/disconnect-agent-oauth.d.ts.map +1 -1
- package/dist/tools/disconnect-agent-oauth.js +32 -12
- package/dist/tools/disconnect-agent-oauth.js.map +1 -1
- package/dist/tools/get-account.d.ts.map +1 -1
- package/dist/tools/get-account.js +148 -42
- package/dist/tools/get-account.js.map +1 -1
- package/dist/tools/get-agent-activity.d.ts.map +1 -1
- package/dist/tools/get-agent-activity.js +185 -35
- package/dist/tools/get-agent-activity.js.map +1 -1
- package/dist/tools/get-agent-conversation.d.ts.map +1 -1
- package/dist/tools/get-agent-conversation.js +57 -35
- package/dist/tools/get-agent-conversation.js.map +1 -1
- package/dist/tools/get-agent-message.d.ts.map +1 -1
- package/dist/tools/get-agent-message.js +6 -4
- package/dist/tools/get-agent-message.js.map +1 -1
- package/dist/tools/get-agent-template.d.ts.map +1 -1
- package/dist/tools/get-agent-template.js +13 -17
- package/dist/tools/get-agent-template.js.map +1 -1
- package/dist/tools/get-model-schema.js +3 -3
- package/dist/tools/get-model-schema.js.map +1 -1
- package/dist/tools/get-my-agent.d.ts.map +1 -1
- package/dist/tools/get-my-agent.js +13 -8
- package/dist/tools/get-my-agent.js.map +1 -1
- package/dist/tools/get-playbook.d.ts.map +1 -1
- package/dist/tools/get-playbook.js +3 -2
- package/dist/tools/get-playbook.js.map +1 -1
- package/dist/tools/get-task-price.js +3 -3
- package/dist/tools/get-task-price.js.map +1 -1
- package/dist/tools/get-task.js +3 -3
- package/dist/tools/get-task.js.map +1 -1
- package/dist/tools/get-workflow.d.ts.map +1 -1
- package/dist/tools/get-workflow.js +38 -19
- package/dist/tools/get-workflow.js.map +1 -1
- package/dist/tools/index.d.ts +3 -1
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +6 -1
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/kill-task.js +3 -3
- package/dist/tools/kill-task.js.map +1 -1
- package/dist/tools/list-agent-skills.d.ts.map +1 -1
- package/dist/tools/list-agent-skills.js +11 -8
- package/dist/tools/list-agent-skills.js.map +1 -1
- package/dist/tools/list-agent-templates.js +4 -4
- package/dist/tools/list-agent-templates.js.map +1 -1
- package/dist/tools/list-my-agents.d.ts.map +1 -1
- package/dist/tools/list-my-agents.js +11 -8
- package/dist/tools/list-my-agents.js.map +1 -1
- package/dist/tools/list-projects.d.ts +4 -0
- package/dist/tools/list-projects.d.ts.map +1 -0
- package/dist/tools/list-projects.js +141 -0
- package/dist/tools/list-projects.js.map +1 -0
- package/dist/tools/list-studio-results.js +3 -3
- package/dist/tools/list-studio-results.js.map +1 -1
- package/dist/tools/list-tasks.js +3 -3
- package/dist/tools/list-tasks.js.map +1 -1
- package/dist/tools/list-workflow-runs.d.ts.map +1 -1
- package/dist/tools/list-workflow-runs.js +6 -4
- package/dist/tools/list-workflow-runs.js.map +1 -1
- package/dist/tools/list-workflows.d.ts.map +1 -1
- package/dist/tools/list-workflows.js +7 -6
- package/dist/tools/list-workflows.js.map +1 -1
- package/dist/tools/manage-agent-billing.d.ts.map +1 -1
- package/dist/tools/manage-agent-billing.js +207 -59
- package/dist/tools/manage-agent-billing.js.map +1 -1
- package/dist/tools/manage-agent-custom-skill.d.ts.map +1 -1
- package/dist/tools/manage-agent-custom-skill.js +119 -15
- package/dist/tools/manage-agent-custom-skill.js.map +1 -1
- package/dist/tools/recommend-model.js +3 -3
- package/dist/tools/recommend-model.js.map +1 -1
- package/dist/tools/resume-workflow-run.d.ts.map +1 -1
- package/dist/tools/resume-workflow-run.js +3 -2
- package/dist/tools/resume-workflow-run.js.map +1 -1
- package/dist/tools/run-model.js +3 -3
- package/dist/tools/run-model.js.map +1 -1
- package/dist/tools/run-workflow.d.ts.map +1 -1
- package/dist/tools/run-workflow.js +3 -2
- package/dist/tools/run-workflow.js.map +1 -1
- package/dist/tools/save-workflow-from-agent.d.ts.map +1 -1
- package/dist/tools/save-workflow-from-agent.js +17 -12
- package/dist/tools/save-workflow-from-agent.js.map +1 -1
- package/dist/tools/schemas.d.ts +32 -26
- package/dist/tools/schemas.d.ts.map +1 -1
- package/dist/tools/schemas.js +67 -0
- package/dist/tools/schemas.js.map +1 -1
- package/dist/tools/search-docs.js +3 -3
- package/dist/tools/search-docs.js.map +1 -1
- package/dist/tools/search-models.js +3 -3
- package/dist/tools/search-models.js.map +1 -1
- package/dist/tools/send-agent-message.d.ts.map +1 -1
- package/dist/tools/send-agent-message.js +30 -9
- package/dist/tools/send-agent-message.js.map +1 -1
- package/dist/tools/set-agent-credentials.d.ts.map +1 -1
- package/dist/tools/set-agent-credentials.js +55 -61
- package/dist/tools/set-agent-credentials.js.map +1 -1
- package/dist/tools/update-agent.d.ts.map +1 -1
- package/dist/tools/update-agent.js +59 -20
- package/dist/tools/update-agent.js.map +1 -1
- package/dist/tools/update-workflow.d.ts +199 -0
- package/dist/tools/update-workflow.d.ts.map +1 -1
- package/dist/tools/update-workflow.js +20 -7
- package/dist/tools/update-workflow.js.map +1 -1
- package/dist/tools/upload-file.js +3 -3
- package/dist/tools/upload-file.js.map +1 -1
- package/dist/tools/wait-for-task.js +3 -3
- package/dist/tools/wait-for-task.js.map +1 -1
- package/dist/tools/wait-for-workflow-run.d.ts +3 -3
- package/dist/tools/wait-for-workflow-run.d.ts.map +1 -1
- package/dist/tools/wait-for-workflow-run.js +7 -6
- package/dist/tools/wait-for-workflow-run.js.map +1 -1
- package/dist/utils/agent-catalog.d.ts +59 -0
- package/dist/utils/agent-catalog.d.ts.map +1 -1
- package/dist/utils/agent-catalog.js +28 -7
- package/dist/utils/agent-catalog.js.map +1 -1
- package/dist/utils/agent-config.d.ts +172 -9
- package/dist/utils/agent-config.d.ts.map +1 -1
- package/dist/utils/agent-config.js +416 -60
- package/dist/utils/agent-config.js.map +1 -1
- package/dist/utils/agent-message.d.ts +36 -14
- package/dist/utils/agent-message.d.ts.map +1 -1
- package/dist/utils/agent-message.js +92 -26
- package/dist/utils/agent-message.js.map +1 -1
- package/dist/utils/agent-targets.d.ts +7 -3
- package/dist/utils/agent-targets.d.ts.map +1 -1
- package/dist/utils/agent-targets.js +35 -23
- package/dist/utils/agent-targets.js.map +1 -1
- package/dist/utils/agent-turns.d.ts +19 -12
- package/dist/utils/agent-turns.d.ts.map +1 -1
- package/dist/utils/agent-turns.js +97 -15
- package/dist/utils/agent-turns.js.map +1 -1
- package/dist/utils/api.d.ts +6 -0
- package/dist/utils/api.d.ts.map +1 -1
- package/dist/utils/api.js +22 -1
- package/dist/utils/api.js.map +1 -1
- package/dist/utils/format.d.ts.map +1 -1
- package/dist/utils/format.js +7 -13
- package/dist/utils/format.js.map +1 -1
- package/dist/utils/json-schema-dialect.d.ts.map +1 -1
- package/dist/utils/json-schema-dialect.js +4 -0
- package/dist/utils/json-schema-dialect.js.map +1 -1
- package/dist/utils/media.d.ts +8 -0
- package/dist/utils/media.d.ts.map +1 -1
- package/dist/utils/media.js +29 -0
- package/dist/utils/media.js.map +1 -1
- package/dist/utils/poll.d.ts +5 -2
- package/dist/utils/poll.d.ts.map +1 -1
- package/dist/utils/poll.js +10 -9
- package/dist/utils/poll.js.map +1 -1
- package/dist/utils/quoted-text.d.ts +28 -0
- package/dist/utils/quoted-text.d.ts.map +1 -0
- package/dist/utils/quoted-text.js +64 -0
- package/dist/utils/quoted-text.js.map +1 -0
- package/dist/utils/studio-runs.d.ts.map +1 -1
- package/dist/utils/studio-runs.js +7 -4
- package/dist/utils/studio-runs.js.map +1 -1
- package/dist/utils/user-agent.d.ts +197 -29
- package/dist/utils/user-agent.d.ts.map +1 -1
- package/dist/utils/user-agent.js +314 -48
- package/dist/utils/user-agent.js.map +1 -1
- package/dist/utils/wiro-project.d.ts +45 -0
- package/dist/utils/wiro-project.d.ts.map +1 -0
- package/dist/utils/wiro-project.js +66 -0
- package/dist/utils/wiro-project.js.map +1 -0
- package/dist/utils/workflow-save.d.ts +22 -1
- package/dist/utils/workflow-save.d.ts.map +1 -1
- package/dist/utils/workflow-save.js +122 -24
- package/dist/utils/workflow-save.js.map +1 -1
- package/dist/utils/workflow.d.ts +26 -13
- package/dist/utils/workflow.d.ts.map +1 -1
- package/dist/utils/workflow.js +51 -20
- package/dist/utils/workflow.js.map +1 -1
- package/dist/utils/workspace.d.ts +30 -4
- package/dist/utils/workspace.d.ts.map +1 -1
- package/dist/utils/workspace.js +95 -12
- package/dist/utils/workspace.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,17 +1,27 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { asString, errorResult, errorText, isPlainObject, requireApiSuccess, toNumber, unixToIso, } from '../utils/api.js';
|
|
3
|
-
import { agentStatusLabel, agentStatusOf, checkSkillSet, credentialsNeededFor, describeSkillsApplyError, diffSkills, enabledSkillNames,
|
|
4
|
-
import { asTier, outcomeUnknown, toTierPricing } from '../utils/user-agent.js';
|
|
3
|
+
import { agentStatusLabel, agentStatusOf, checkSkillSet, credentialLabel, credentialTitles, credentialsNeededFor, customSkillEntries, daysLeftText, describeSkillsApplyError, diffSkills, enabledSkillNames, formatUsd, isAgentRunning, loadSkillRegistry, newSkillChangeKey, parseSkillChangeKey, pausedSkillTitles, planBilling, planSkillChanges, readPlanChange, readUserAgent, registryTitles, underOneDayLeft, restartNote, skillChangeDigest, skillPriceTag, workspaceHint, } from '../utils/agent-config.js';
|
|
4
|
+
import { asTier, bindProjectStep, formatCredits, outcomeUnknown, skillLabel, skillOffTasksNote, taskLabel, taskNamedByWiro, toStringArray, toTierPricing, WIRO_PROJECT_CREDENTIAL } from '../utils/user-agent.js';
|
|
5
5
|
import { DEFAULT_WAIT_SECONDS } from '../utils/poll.js';
|
|
6
6
|
import { isPlainTeamMember, teamAgentBillingRefused } from '../utils/workspace.js';
|
|
7
|
-
import { nextActionSchema } from './schemas.js';
|
|
7
|
+
import { nextActionSchema, strictInput } from './schemas.js';
|
|
8
8
|
const pricingSchema = z.object({
|
|
9
9
|
currentMonthlyUsd: z.number().nullable(),
|
|
10
10
|
newMonthlyUsd: z.number().nullable(),
|
|
11
11
|
deltaMonthlyUsd: z.number().nullable(),
|
|
12
|
-
currentMonthlyCredits: z.number().nullable()
|
|
13
|
-
|
|
12
|
+
currentMonthlyCredits: z.number().nullable()
|
|
13
|
+
.describe('The monthly credits each renewal grants now.'),
|
|
14
|
+
newMonthlyCredits: z.number().nullable()
|
|
15
|
+
.describe('The monthly credits each renewal grants after the change.'),
|
|
14
16
|
deltaMonthlyCredits: z.number().nullable(),
|
|
17
|
+
creditsNow: z.number().optional()
|
|
18
|
+
.describe('The credits this period gains now, below 0 when it loses them: the change counts for the time left in the period.'),
|
|
19
|
+
creditsThisPeriod: z.number().optional()
|
|
20
|
+
.describe('The monthly credits this period grants in all after the change.'),
|
|
21
|
+
creditsFromRenewal: z.number().optional()
|
|
22
|
+
.describe('The monthly credits every renewal grants after the change.'),
|
|
23
|
+
periodEnd: z.string().optional()
|
|
24
|
+
.describe('When the current period ends.'),
|
|
15
25
|
note: z.string().optional(),
|
|
16
26
|
});
|
|
17
27
|
const outputSchema = {
|
|
@@ -34,7 +44,12 @@ const outputSchema = {
|
|
|
34
44
|
plan: z.enum(['prepaid', 'card', 'none']),
|
|
35
45
|
subscriptionStatus: z.string().nullable(),
|
|
36
46
|
wallet: z.enum(['team', 'personal']),
|
|
37
|
-
estimatedChargeNowUsd: z.number().nullable().optional()
|
|
47
|
+
estimatedChargeNowUsd: z.number().nullable().optional()
|
|
48
|
+
.describe('What a wallet plan pays now at list price, as Wiro works it out, before any discount on the wallet; 0 on a card plan, whose difference goes to the card subscription.'),
|
|
49
|
+
creditNowUsd: z.number().nullable().optional()
|
|
50
|
+
.describe('For a drop on a wallet plan, what goes back to the balances that paid; null when it is worked out when the change is applied.'),
|
|
51
|
+
usedCreditsUsd: z.number().nullable().optional()
|
|
52
|
+
.describe('The part of a credit that comes off for credits already used above what the plan keeps.'),
|
|
38
53
|
chargedNowUsd: z.number().nullable().optional(),
|
|
39
54
|
note: z.string(),
|
|
40
55
|
}),
|
|
@@ -43,6 +58,15 @@ const outputSchema = {
|
|
|
43
58
|
skill: z.string(),
|
|
44
59
|
credentials: z.array(z.string()),
|
|
45
60
|
})),
|
|
61
|
+
scheduledTasks: z.object({
|
|
62
|
+
stop: z.array(z.string())
|
|
63
|
+
.describe('Scheduled tasks this change stops: they run now, or would once an agent that is not running starts. They keep their text, schedule and on/off state, and get_my_agent then shows what each waits for.'),
|
|
64
|
+
resume: z.array(z.string())
|
|
65
|
+
.describe('Scheduled tasks that do not run now and start running once this change is applied, and the agent is running.'),
|
|
66
|
+
}).nullable().optional()
|
|
67
|
+
.describe('Quote: the scheduled tasks this change stops or starts, as Wiro works it out; null when Wiro could not tell.'),
|
|
68
|
+
pausedTasks: z.array(z.string()).optional()
|
|
69
|
+
.describe('After an applied change: the scheduled tasks it paused, which wait for skills that are off. get_my_agent names those skills.'),
|
|
46
70
|
agentStatus: z.string(),
|
|
47
71
|
restart: z.string(),
|
|
48
72
|
error: z.object({
|
|
@@ -56,9 +80,64 @@ const outputSchema = {
|
|
|
56
80
|
nextAction: nextActionSchema.optional(),
|
|
57
81
|
};
|
|
58
82
|
const UNCHANGED_RESTART = 'Nothing changed, so the agent does not restart.';
|
|
59
|
-
//
|
|
60
|
-
|
|
61
|
-
|
|
83
|
+
// The tier and the price live with Wiro, and the agent reads neither: only a skill that turns on or off restarts it.
|
|
84
|
+
const TIER_ONLY_RESTART = 'No skill turns on or off, so a change of the tier alone does not restart the agent.';
|
|
85
|
+
function roundCents(usd) {
|
|
86
|
+
return Math.round(usd * 100) / 100;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* How long an applied credit lasts, from the parts Wiro says it split into. Each
|
|
90
|
+
* part went back to the balance that paid it and keeps that balance's end date,
|
|
91
|
+
* except the part whose source has ended since (each ...UntilPeriodEndUsd): that
|
|
92
|
+
* part lasts until the plan's current period ends. The two amounts add up to the
|
|
93
|
+
* credit, and parts that come to nothing say nothing.
|
|
94
|
+
*/
|
|
95
|
+
function creditTerms(parts) {
|
|
96
|
+
if (!isPlainObject(parts))
|
|
97
|
+
return '';
|
|
98
|
+
const usd = (key) => toNumber(parts[key]) ?? 0;
|
|
99
|
+
const untilEnd = roundCents(usd('couponUntilPeriodEndUsd') + usd('storeUntilPeriodEndUsd') + usd('depositUntilPeriodEndUsd'));
|
|
100
|
+
const keepsEnd = roundCents(roundCents(usd('couponUsd') + usd('storeUsd') + usd('depositUsd')) - untilEnd);
|
|
101
|
+
if (keepsEnd > 0 && untilEnd > 0) {
|
|
102
|
+
return `${formatUsd(keepsEnd)} of it went back to the balances that paid and keeps their end dates, and ${formatUsd(untilEnd)} lasts until this period ends, because the credit that paid it has ended.`;
|
|
103
|
+
}
|
|
104
|
+
if (keepsEnd > 0)
|
|
105
|
+
return 'It went back to the balances that paid and keeps their end dates.';
|
|
106
|
+
return untilEnd > 0 ? 'It lasts until this period ends, because the credit that paid it has ended.' : '';
|
|
107
|
+
}
|
|
108
|
+
/** A count of credits with its sign, such as +97 or -690. */
|
|
109
|
+
function signedCredits(credits) {
|
|
110
|
+
return `${credits > 0 ? '+' : ''}${formatCredits(credits)}`;
|
|
111
|
+
}
|
|
112
|
+
/** The fields of Wiro's plan change that the pricing output carries. */
|
|
113
|
+
function periodFields(change) {
|
|
114
|
+
if (!change)
|
|
115
|
+
return {};
|
|
116
|
+
const periodEnd = unixToIso(change.periodEnd);
|
|
117
|
+
return {
|
|
118
|
+
...(change.creditsNow !== null ? { creditsNow: change.creditsNow } : {}),
|
|
119
|
+
...(change.creditsThisPeriod !== null ? { creditsThisPeriod: change.creditsThisPeriod } : {}),
|
|
120
|
+
...(change.creditsFromRenewal !== null ? { creditsFromRenewal: change.creditsFromRenewal } : {}),
|
|
121
|
+
...(periodEnd ? { periodEnd } : {}),
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* What a change does to this period's credits when none come with it: those it takes away end for the
|
|
126
|
+
* time left, and the period keeps the rest. A plan priced again at today's rates can also keep its
|
|
127
|
+
* credits, or gain some, while its price drops.
|
|
128
|
+
*/
|
|
129
|
+
function creditsEndNote(skillsOff, creditsNow, creditsThisPeriod) {
|
|
130
|
+
if (creditsNow !== null && creditsNow > 0) {
|
|
131
|
+
return `This period gains ${formatCredits(creditsNow)} credits for the time left${creditsThisPeriod !== null ? `, ${formatCredits(creditsThisPeriod)} in all` : ''}.`;
|
|
132
|
+
}
|
|
133
|
+
if (creditsNow === 0) {
|
|
134
|
+
return creditsThisPeriod !== null ? `This period keeps its ${formatCredits(creditsThisPeriod)} credits.` : 'This period keeps its credits.';
|
|
135
|
+
}
|
|
136
|
+
const keeps = creditsThisPeriod !== null ? `, and this period keeps ${formatCredits(creditsThisPeriod)}` : '';
|
|
137
|
+
if (skillsOff === 0)
|
|
138
|
+
return `The credits the change takes away end now for the time left${keeps}.`;
|
|
139
|
+
return `${skillsOff === 1 ? 'Its' : 'Their'} credits for the time left end now${keeps}.`;
|
|
140
|
+
}
|
|
62
141
|
function difference(next, current) {
|
|
63
142
|
return next === null || current === null ? null : Math.round((next - current) * 100) / 100;
|
|
64
143
|
}
|
|
@@ -66,9 +145,10 @@ function difference(next, current) {
|
|
|
66
145
|
* A skill change prices the plan at today's prices for all its skills, so a
|
|
67
146
|
* plan that started at other prices also changes for the skills it already
|
|
68
147
|
* has, and so do its monthly credits. The agent's tiers carry today's prices
|
|
69
|
-
* and credits for those skills.
|
|
70
|
-
*
|
|
71
|
-
* prices, which the quote
|
|
148
|
+
* and credits for those skills. They are the base of the new price, not a price
|
|
149
|
+
* the plan reaches. An agent without an active plan cannot take the change yet,
|
|
150
|
+
* and a move to Pro prices the whole plan at today's Pro prices, which the quote
|
|
151
|
+
* already shows.
|
|
72
152
|
*/
|
|
73
153
|
function todayPriceNote(row, billing, currentPrice, currentCredits, tierChange) {
|
|
74
154
|
if (tierChange)
|
|
@@ -76,12 +156,12 @@ function todayPriceNote(row, billing, currentPrice, currentCredits, tierChange)
|
|
|
76
156
|
const tier = asTier(row.tier);
|
|
77
157
|
const tiers = toTierPricing(isPlainObject(row.agent) ? row.agent.tiers : undefined);
|
|
78
158
|
const today = tier && tiers ? tiers[tier] : null;
|
|
79
|
-
if (billing.
|
|
159
|
+
if (billing.status !== 'active' || currentPrice === null || today === null || !difference(today.priceUsd, currentPrice))
|
|
80
160
|
return null;
|
|
81
|
-
const
|
|
82
|
-
?
|
|
83
|
-
:
|
|
84
|
-
return `
|
|
161
|
+
const versus = currentCredits !== null && today.monthlyCredits !== currentCredits
|
|
162
|
+
? ` with ${formatCredits(today.monthlyCredits)} monthly credits, not its current ${formatUsd(currentPrice)} and ${formatCredits(currentCredits)}`
|
|
163
|
+
: `, not its current ${formatUsd(currentPrice)}`;
|
|
164
|
+
return `Today's ${tier === 'pro' ? 'Pro' : 'Starter'} price for the skills this plan already has is ${formatUsd(today.priceUsd)} a month${versus}. A skill change counts from today's price, and the new price in this quote already includes that.`;
|
|
85
165
|
}
|
|
86
166
|
/**
|
|
87
167
|
* The wallet of the agent's workspace, which Wiro charges or credits for a
|
|
@@ -92,56 +172,175 @@ function todayPriceNote(row, billing, currentPrice, currentCredits, tierChange)
|
|
|
92
172
|
function walletName(billing) {
|
|
93
173
|
return billing.wallet === 'team' ? 'the team wallet' : 'your personal wallet';
|
|
94
174
|
}
|
|
95
|
-
/**
|
|
175
|
+
/** When the new monthly credits come: a plan set to end renews only if it is kept, and an agent without a plan gets them once it is subscribed. */
|
|
176
|
+
function creditsWhen(billing) {
|
|
177
|
+
if (billing.plan === 'none')
|
|
178
|
+
return 'a month once it is subscribed';
|
|
179
|
+
return billing.status === 'active' && billing.cancelAtPeriodEnd === true
|
|
180
|
+
? 'at each renewal after the change, if the plan is kept'
|
|
181
|
+
: 'at each renewal after the change';
|
|
182
|
+
}
|
|
183
|
+
/** When an active plan's next renewal gives the new monthly credits. A card plan whose renewal is not known names it as a condition. */
|
|
184
|
+
function renewalWhen(billing) {
|
|
185
|
+
if (billing.cancelAtPeriodEnd === true)
|
|
186
|
+
return 'from the next renewal, if the plan is kept';
|
|
187
|
+
return billing.cancelAtPeriodEnd === null ? 'from the next renewal, if the plan renews' : 'from the next renewal';
|
|
188
|
+
}
|
|
189
|
+
/** What renewals give after an applied change: a plan that may not renew names it as a condition, as its quote did. */
|
|
190
|
+
function renewalGives(billing, credits) {
|
|
191
|
+
return billing.cancelAtPeriodEnd === false ? `every renewal gives ${formatCredits(credits)}` : `${formatCredits(credits)} ${renewalWhen(billing)}`;
|
|
192
|
+
}
|
|
193
|
+
/** For a cancelled plan, a skill change only covers the time left: nothing renews at the new price. */
|
|
96
194
|
function cancelledNote(billing) {
|
|
97
195
|
const end = billing.periodEnd !== null ? unixToIso(billing.periodEnd) : undefined;
|
|
98
196
|
if (billing.plan === 'card') {
|
|
99
197
|
return `The plan is set to end${end ? ` after ${end}` : ' with this period'} and does not renew.`;
|
|
100
198
|
}
|
|
101
|
-
return `The plan is cancelled and ends about a day after ${end ?? 'the end of this period'}, so this covers only the
|
|
199
|
+
return `The plan is cancelled and ends about a day after ${end ?? 'the end of this period'}, so this covers only the time left and the new price is not renewed. Undo the cancellation with manage_agent_billing (renew) to keep the plan.`;
|
|
102
200
|
}
|
|
103
|
-
|
|
201
|
+
/** A card subscription that ends with this period: the plan is set to end, or it moves to wallet billing. */
|
|
202
|
+
function cardEndsWithPeriod(billing) {
|
|
203
|
+
return billing.plan === 'card' && (billing.cancelAtPeriodEnd === true || billing.switchingToWallet);
|
|
204
|
+
}
|
|
205
|
+
function walletSwitchNote(billing, price) {
|
|
206
|
+
return `When this period ends, the plan moves to wallet billing and renews from ${walletName(billing)}${price !== null ? ` at ${formatUsd(price)} a month` : ''}.`;
|
|
207
|
+
}
|
|
208
|
+
// A card subscription that ends with this period is not renewed by card, so its notes point to no next invoice.
|
|
209
|
+
const CARD_ENDS_QUOTE = 'Nothing is charged now. The card subscription is updated to the new price for the rest of this period.';
|
|
210
|
+
// A card plan whose renewal is not known may also end with this period, so its next invoice is named only for a plan that renews by card.
|
|
211
|
+
const CARD_UNKNOWN_QUOTE = 'Nothing is charged now. The card subscription is updated to the new price, and a plan that renews by card shows the prorated difference on its next invoice.';
|
|
212
|
+
/** Only a wallet plan that has ended comes back with renew; any other plan that is not active takes a new one (manage_agent_billing). */
|
|
213
|
+
function renewsBack(billing) {
|
|
214
|
+
return billing.plan === 'prepaid' && billing.status === 'expired';
|
|
215
|
+
}
|
|
216
|
+
/** What a price drop on a wallet plan gives back now, from Wiro's own plan change. */
|
|
217
|
+
function walletCreditNote(change) {
|
|
218
|
+
const used = change.usedCreditsUsd ?? 0;
|
|
219
|
+
// A credit is at most 1 cent for each credit the change takes away unused, so one that takes none away gives nothing back.
|
|
220
|
+
if (change.creditNowUsd === 0 && used > 0 && change.creditsNow !== null && change.creditsNow >= 0) {
|
|
221
|
+
return 'Nothing comes back: the change takes no credits away.';
|
|
222
|
+
}
|
|
223
|
+
if (change.creditNowUsd === null) {
|
|
224
|
+
return 'What comes back is worked out when the change is applied: what the time left is worth, less 1 cent for each credit already used above what the plan keeps.';
|
|
225
|
+
}
|
|
226
|
+
if (change.creditNowUsd > 0) {
|
|
227
|
+
return `${formatUsd(change.creditNowUsd)} goes back to the balances that paid, and each keeps its own end date.`
|
|
228
|
+
+ (used > 0 ? ` ${formatUsd(used)} comes off for credits already used above what the plan keeps.` : '');
|
|
229
|
+
}
|
|
230
|
+
return used > 0
|
|
231
|
+
? 'Nothing comes back: the credits already used are worth more than the time left.'
|
|
232
|
+
: 'Nothing comes back to the wallet now.';
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* The quote's billing line. An active plan's money and credits come from the
|
|
236
|
+
* plan change Wiro works out on the plan as it is now (change); without it,
|
|
237
|
+
* the line says what happens without a number.
|
|
238
|
+
*/
|
|
239
|
+
function quoteBillingNote(billing, delta, change, nextPrice, skillsOff) {
|
|
104
240
|
// A card plan whose renewal is not known gets the new price without a renewal claim.
|
|
105
241
|
const after = billing.cancelAtPeriodEnd === true
|
|
106
242
|
? ` ${cancelledNote(billing)}`
|
|
107
|
-
:
|
|
108
|
-
?
|
|
109
|
-
:
|
|
110
|
-
?
|
|
111
|
-
:
|
|
243
|
+
: billing.switchingToWallet
|
|
244
|
+
? ` ${walletSwitchNote(billing, nextPrice)}`
|
|
245
|
+
: nextPrice === null
|
|
246
|
+
? ''
|
|
247
|
+
: billing.cancelAtPeriodEnd === false
|
|
248
|
+
? ` The plan then renews at ${formatUsd(nextPrice)} a month.`
|
|
249
|
+
: ` The new monthly price is ${formatUsd(nextPrice)}.`;
|
|
112
250
|
if (billing.plan === 'none') {
|
|
113
251
|
return 'The agent has no subscription yet, so nothing is charged now. The new monthly price applies once it is subscribed.';
|
|
114
252
|
}
|
|
115
253
|
if (billing.status !== 'active') {
|
|
116
|
-
return
|
|
254
|
+
return renewsBack(billing)
|
|
255
|
+
? `The subscription is ${billing.status ?? 'not active'}. Skills can only change on an active subscription, so renew it with manage_agent_billing first.`
|
|
256
|
+
: `The subscription is ${billing.status ?? 'not active'}. Skills can only change on an active subscription, so start a new plan with manage_agent_billing (subscribe) first.`;
|
|
117
257
|
}
|
|
118
|
-
if (delta
|
|
258
|
+
if (delta !== null && Math.abs(delta) < 0.005)
|
|
259
|
+
return 'The monthly price does not change, so nothing is charged.';
|
|
260
|
+
if (delta === null || change === null) {
|
|
119
261
|
return billing.plan === 'prepaid'
|
|
120
|
-
? `The difference for the rest of this period is taken from, or credited to, ${walletName(billing)} when the change is applied. A credit
|
|
121
|
-
:
|
|
262
|
+
? `The difference for the rest of this period is taken from, or credited to, ${walletName(billing)} when the change is applied. A credit goes back to the balances that paid, and each keeps its own end date.${after}`
|
|
263
|
+
: cardEndsWithPeriod(billing)
|
|
264
|
+
? `${CARD_ENDS_QUOTE}${after}`
|
|
265
|
+
: billing.cancelAtPeriodEnd === null
|
|
266
|
+
? `${CARD_UNKNOWN_QUOTE}${after}`
|
|
267
|
+
: `Any prorated difference appears on the next invoice.${after}`;
|
|
122
268
|
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
269
|
+
const ends = creditsEndNote(skillsOff, change.creditsNow, change.creditsThisPeriod);
|
|
270
|
+
// Credits come with a rise when this period gains them. A plan priced again at today's rates can also
|
|
271
|
+
// lose credits, or keep them, while its price rises.
|
|
272
|
+
const gained = change.creditsNow !== null && change.creditsNow > 0 ? change.creditsNow : null;
|
|
273
|
+
const notGained = change.creditsNow !== null && change.creditsNow <= 0 ? ` ${ends}` : '';
|
|
274
|
+
if (!change.wallet) {
|
|
275
|
+
if (delta > 0) {
|
|
276
|
+
const added = gained !== null ? ` ${formatCredits(gained)} credits come now for the rest of this period.` : notGained;
|
|
277
|
+
if (cardEndsWithPeriod(billing))
|
|
278
|
+
return `${CARD_ENDS_QUOTE}${added}${after}`;
|
|
279
|
+
if (billing.cancelAtPeriodEnd === null)
|
|
280
|
+
return `${CARD_UNKNOWN_QUOTE}${added}${after}`;
|
|
281
|
+
return gained !== null
|
|
282
|
+
? `Nothing is charged now. The prorated difference appears on the next invoice, and ${formatCredits(gained)} credits come now for the rest of this period.${after}`
|
|
283
|
+
: `Nothing is charged now. The prorated difference appears on the next invoice.${notGained}${after}`;
|
|
284
|
+
}
|
|
285
|
+
if (cardEndsWithPeriod(billing))
|
|
286
|
+
return `${CARD_ENDS_QUOTE} ${ends}${after}`;
|
|
287
|
+
if (billing.cancelAtPeriodEnd === null)
|
|
288
|
+
return `${CARD_UNKNOWN_QUOTE} ${ends}${after}`;
|
|
289
|
+
return `The prorated difference comes off the next invoice. ${ends}${after}`;
|
|
127
290
|
}
|
|
128
|
-
const days = billing.daysRemaining !== null ? ` for the ${billing.daysRemaining} days left in this period` : ' for the rest of this period';
|
|
129
291
|
if (delta > 0) {
|
|
130
|
-
|
|
292
|
+
const charge = formatUsd(change.chargeNowUsd ?? 0);
|
|
293
|
+
const credits = gained !== null ? `, and ${formatCredits(gained)} credits come with it` : '';
|
|
294
|
+
const days = daysLeftText(change.secondsLeft);
|
|
295
|
+
return underOneDayLeft(change.secondsLeft)
|
|
296
|
+
? `${charge} is taken from ${walletName(billing)} now at the one-day minimum${credits}.${notGained}${after}`
|
|
297
|
+
: `${charge} is taken from ${walletName(billing)} now for the rest of this period${days ? ` (${days})` : ''}${credits}.${notGained}${after}`;
|
|
131
298
|
}
|
|
132
|
-
return
|
|
299
|
+
return `${walletCreditNote(change)} ${ends}${after}`;
|
|
133
300
|
}
|
|
134
301
|
function chargeNote(billing, body) {
|
|
135
302
|
const charged = toNumber(body.prepaidWalletDelta);
|
|
303
|
+
const used = toNumber(body.usedCreditsUsd) ?? 0;
|
|
304
|
+
const creditsNow = isPlainObject(body.pricing) ? toNumber(body.pricing.creditsNow) : null;
|
|
136
305
|
const cancelled = billing.cancelAtPeriodEnd === true && billing.status === 'active' ? ` ${cancelledNote(billing)}` : '';
|
|
137
306
|
if (charged !== null && charged > 0) {
|
|
138
|
-
|
|
307
|
+
const credits = creditsNow !== null && creditsNow > 0 ? `, and ${formatCredits(creditsNow)} credits came with it` : '';
|
|
308
|
+
return { charged, note: `${formatUsd(charged)} was taken from ${walletName(billing)} for the rest of this period${credits}.${cancelled}` };
|
|
139
309
|
}
|
|
140
310
|
if (charged !== null && charged < 0) {
|
|
141
|
-
|
|
311
|
+
const terms = creditTerms(body.prepaidCreditParts);
|
|
312
|
+
const usedNote = used > 0 ? ` ${formatUsd(used)} came off for credits already used above what the plan keeps.` : '';
|
|
313
|
+
return { charged, note: `${formatUsd(Math.abs(charged))} was credited to ${walletName(billing)}.${terms ? ` ${terms}` : ''}${usedNote}${cancelled}` };
|
|
314
|
+
}
|
|
315
|
+
// A change that took no credits away had nothing to credit, and nothing failed.
|
|
316
|
+
if (billing.plan === 'prepaid' && used > 0 && creditsNow !== null && creditsNow >= 0) {
|
|
317
|
+
return { charged: null, note: `Nothing came back: the change took no credits away.${cancelled}` };
|
|
318
|
+
}
|
|
319
|
+
// The credits already used came to more than the time left was worth: no credit, and nothing failed.
|
|
320
|
+
// A part Wiro could not add (prepaidCreditFailedUsd) was due all the same: its warning follows, and the
|
|
321
|
+
// credits used took only what Wiro names.
|
|
322
|
+
if (billing.plan === 'prepaid' && used > 0) {
|
|
323
|
+
return (toNumber(body.prepaidCreditFailedUsd) ?? 0) > 0
|
|
324
|
+
? { charged: null, note: `Nothing came back now. ${formatUsd(used)} came off for credits already used above what the plan keeps.${cancelled}` }
|
|
325
|
+
: { charged: null, note: `Nothing came back: the credits already used were worth more than the time left.${cancelled}` };
|
|
142
326
|
}
|
|
143
327
|
if (body.stripeProrationApplied === true) {
|
|
144
|
-
|
|
328
|
+
const moved = isPlainObject(body.pricing) ? toNumber(body.pricing.deltaUsd) : null;
|
|
329
|
+
if (moved !== null && Math.abs(moved) < 0.005) {
|
|
330
|
+
return { charged: null, note: `The monthly price did not change, so nothing was charged.${cancelled}` };
|
|
331
|
+
}
|
|
332
|
+
const added = moved !== null && moved > 0 && creditsNow !== null && creditsNow > 0
|
|
333
|
+
? ` ${formatCredits(creditsNow)} credits came now for the rest of this period.`
|
|
334
|
+
: '';
|
|
335
|
+
if (cardEndsWithPeriod(billing)) {
|
|
336
|
+
const price = isPlainObject(body.pricing) ? toNumber(body.pricing.newPriceUsd) : null;
|
|
337
|
+
const switching = billing.switchingToWallet ? ` ${walletSwitchNote(billing, price)}` : '';
|
|
338
|
+
return { charged: null, note: `Nothing was charged now. The card subscription was updated to the new price for the rest of this period.${added}${cancelled}${switching}` };
|
|
339
|
+
}
|
|
340
|
+
if (billing.cancelAtPeriodEnd === null) {
|
|
341
|
+
return { charged: null, note: `Nothing was charged now. The card subscription was updated to the new price, and a plan that renews by card shows the prorated difference on its next invoice.${added}` };
|
|
342
|
+
}
|
|
343
|
+
return { charged: null, note: `The card subscription was updated; any prorated difference appears on the next invoice.${added}${cancelled}` };
|
|
145
344
|
}
|
|
146
345
|
if (billing.plan === 'none') {
|
|
147
346
|
return { charged: null, note: 'Nothing was charged: the agent has no subscription yet.' };
|
|
@@ -156,21 +355,101 @@ function appliedBillingNote(billing, body) {
|
|
|
156
355
|
.filter((warning) => Boolean(warning));
|
|
157
356
|
return warnings.length > 0 ? { ...result, note: [result.note, ...warnings].join(' ') } : result;
|
|
158
357
|
}
|
|
159
|
-
|
|
160
|
-
|
|
358
|
+
/** The sentence for a change whose skills need a Wiro project. */
|
|
359
|
+
function bindProjectNote(needed) {
|
|
360
|
+
return needed.some(entry => entry.credentials.includes(WIRO_PROJECT_CREDENTIAL))
|
|
361
|
+
? ' bind_agent_project connects a Wiro project, with no key to paste.'
|
|
362
|
+
: '';
|
|
363
|
+
}
|
|
364
|
+
/** How to give the new skills what they still need. A Wiro project comes first, since it takes no key. */
|
|
365
|
+
function stillNeededSteps(needed) {
|
|
366
|
+
const keys = needed.flatMap(entry => entry.credentials);
|
|
367
|
+
const fields = 'set_agent_credentials or connect_agent_oauth.';
|
|
368
|
+
if (!keys.includes(WIRO_PROJECT_CREDENTIAL))
|
|
369
|
+
return `Set them with ${fields}`;
|
|
370
|
+
const others = keys.some(key => key !== WIRO_PROJECT_CREDENTIAL) ? ` Set the others with ${fields}` : '';
|
|
371
|
+
return `Connect the Wiro project with bind_agent_project, with no key to paste.${others}`;
|
|
372
|
+
}
|
|
373
|
+
/** A list of skills by title, with the names apply_agent_skills takes. */
|
|
374
|
+
function listLine(label, names, titles) {
|
|
375
|
+
return names.length > 0 ? `**${label}:** ${names.map(name => skillLabel(name, titles)).join(', ')}` : null;
|
|
376
|
+
}
|
|
377
|
+
/** Each new skill with the credentials it still needs, by title: Instagram Post (`int-instagram-post`): Instagram (`instagram`). */
|
|
378
|
+
function neededText(needed, row, titles, serviceTitles) {
|
|
379
|
+
return needed.map(entry => `${skillLabel(entry.skill, titles)}: ${entry.credentials.map(key => credentialLabel(row, key, serviceTitles)).join(', ')}`).join('; ');
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* The scheduled tasks a change stops or starts, as Wiro works them out with its own gates: null when Wiro
|
|
383
|
+
* could not tell (it sends null or nothing), undefined when there are none.
|
|
384
|
+
*/
|
|
385
|
+
function scheduledTasksOf(value) {
|
|
386
|
+
if (!isPlainObject(value))
|
|
387
|
+
return null;
|
|
388
|
+
const stop = toStringArray(value.stop);
|
|
389
|
+
const resume = toStringArray(value.resume);
|
|
390
|
+
return stop.length > 0 || resume.length > 0 ? { stop, resume } : undefined;
|
|
391
|
+
}
|
|
392
|
+
/**
|
|
393
|
+
* The quote's lines for the scheduled tasks the change stops or starts, or that Wiro could not tell. Wiro
|
|
394
|
+
* counts a task as running by its gates, so on an agent that is not running it is a task that would run once
|
|
395
|
+
* the agent starts. Each task is named as people see it.
|
|
396
|
+
*/
|
|
397
|
+
function movedTaskLines(moved, row, running) {
|
|
398
|
+
if (moved === null) {
|
|
399
|
+
return ['**Scheduled tasks:** Wiro could not tell which scheduled tasks this change stops or starts. Once it is applied, the result names the tasks it paused.'];
|
|
400
|
+
}
|
|
401
|
+
if (!moved)
|
|
402
|
+
return [];
|
|
403
|
+
const entries = new Map(customSkillEntries(row).map(entry => [entry.key, entry]));
|
|
404
|
+
const names = (list) => list.map(key => {
|
|
405
|
+
const entry = entries.get(key);
|
|
406
|
+
return taskLabel(key, entry ? taskNamedByWiro(entry) : true);
|
|
407
|
+
}).join(', ');
|
|
408
|
+
const lines = [];
|
|
409
|
+
if (moved.stop.length > 0) {
|
|
410
|
+
const one = moved.stop.length === 1;
|
|
411
|
+
const now = running
|
|
412
|
+
? (one ? 'It runs now and stops with this change.' : 'They run now and stop with this change.')
|
|
413
|
+
: (one ? 'It would run once the agent starts, and this change stops it.' : 'They would run once the agent starts, and this change stops them.');
|
|
414
|
+
lines.push(`**Scheduled tasks that stop:** ${names(moved.stop)}. ${now} ${one ? 'It keeps its' : 'They keep their'} text, schedule and on/off state, and get_my_agent then shows what ${one ? 'it waits' : 'each waits'} for.`);
|
|
415
|
+
}
|
|
416
|
+
if (moved.resume.length > 0) {
|
|
417
|
+
const one = moved.resume.length === 1;
|
|
418
|
+
const after = running ? 'once this change is applied' : 'once this change is applied and the agent starts';
|
|
419
|
+
lines.push(`**Scheduled tasks that start running:** ${names(moved.resume)}. ${one ? 'It does not run now and runs' : 'They do not run now and run'} ${after}.`);
|
|
420
|
+
}
|
|
421
|
+
return lines;
|
|
422
|
+
}
|
|
423
|
+
/**
|
|
424
|
+
* The scheduled tasks the change paused: on and paused in the agent read after it, not paused before it, and
|
|
425
|
+
* among those Wiro said the change stops (stops), when it could tell.
|
|
426
|
+
*/
|
|
427
|
+
function newlyPaused(before, after, stops) {
|
|
428
|
+
if (!after)
|
|
429
|
+
return [];
|
|
430
|
+
const pausedBefore = new Set(customSkillEntries(before).filter(entry => entry.skillOff).map(entry => entry.key));
|
|
431
|
+
return customSkillEntries(after).filter(entry => entry.kind === 'scheduled_task' && entry.enabled && entry.skillOff
|
|
432
|
+
&& !pausedBefore.has(entry.key) && (stops === null || stops.includes(entry.key)));
|
|
161
433
|
}
|
|
162
|
-
/**
|
|
163
|
-
|
|
434
|
+
/**
|
|
435
|
+
* The agent read again after the change, and whether its live skills and tier are what this change asked
|
|
436
|
+
* for; both null when it cannot be read.
|
|
437
|
+
*/
|
|
438
|
+
async function readAfterChange(client, agentGuid, requested, tier, signal) {
|
|
164
439
|
let live;
|
|
165
440
|
try {
|
|
166
441
|
live = await readUserAgent(client, agentGuid, signal);
|
|
167
442
|
}
|
|
168
443
|
catch {
|
|
169
|
-
return null;
|
|
444
|
+
return { holds: null, live: null };
|
|
170
445
|
}
|
|
171
446
|
const enabled = new Set(enabledSkillNames(live));
|
|
172
447
|
const skillsHold = Object.entries(requested).every(([name, on]) => enabled.has(name) === on);
|
|
173
|
-
return skillsHold && (!tier || asString(live.tier) === tier);
|
|
448
|
+
return { holds: skillsHold && (!tier || asString(live.tier) === tier), live };
|
|
449
|
+
}
|
|
450
|
+
/** The line that names the tasks a change paused, with the skills each waits for. */
|
|
451
|
+
async function pausedLine(client, paused, signal) {
|
|
452
|
+
return paused.length > 0 ? `**Paused by this change:**${skillOffTasksNote(paused, await pausedSkillTitles(client, paused, signal), 'is paused')}` : null;
|
|
174
453
|
}
|
|
175
454
|
export function registerApplyAgentSkills(server, client) {
|
|
176
455
|
server.registerTool('apply_agent_skills', {
|
|
@@ -180,21 +459,26 @@ export function registerApplyAgentSkills(server, client) {
|
|
|
180
459
|
+ 'On a team agent only a team admin or the person who deployed the agent can change its skills; other '
|
|
181
460
|
+ 'members are refused and nothing is charged. '
|
|
182
461
|
+ 'Steps: 1) call without confirm. The tool checks the names, adds the skills they '
|
|
183
|
-
+ 'depend on, and returns the current and new monthly price,
|
|
184
|
-
+ '
|
|
462
|
+
+ 'depend on, and returns the current and new monthly price, what Wiro charges or credits now, '
|
|
463
|
+
+ 'the credits this period gains or loses, the scheduled tasks it stops or starts, and an idempotency_key. Nothing changes yet. 2) Show the price to the '
|
|
185
464
|
+ 'user. 3) After they approve, call again with confirm: true and that idempotency_key. '
|
|
186
|
-
+ 'The key is tied to the quoted prices
|
|
187
|
-
+ 'is applied and a new quote with a new key comes back. Reusing the key after a timeout '
|
|
465
|
+
+ 'The key is tied to the quoted prices and, on a prepaid plan, the billing period: if '
|
|
466
|
+
+ 'either changed since the quote, nothing is applied and a new quote with a new key comes back. Reusing the key after a timeout '
|
|
188
467
|
+ 'is safe, the change is applied once. Billing: on a prepaid plan the monthly difference '
|
|
189
|
-
+ 'for the
|
|
190
|
-
+ '
|
|
191
|
-
+ '
|
|
192
|
-
+ '
|
|
193
|
-
+ '
|
|
468
|
+
+ 'for the time left in the period is taken from the wallet at once, and the added credits come '
|
|
469
|
+
+ 'for that same time; every renewal gives all of them. When the price drops, what the time left '
|
|
470
|
+
+ 'is worth goes back to the balances that paid, less 1 cent for each credit already used above '
|
|
471
|
+
+ 'what the plan keeps, and each balance keeps its own end date. A skill that comes off loses its '
|
|
472
|
+
+ 'credits for the time left; the ones the time already passed paid for stay until the renewal. '
|
|
473
|
+
+ 'On a card plan nothing is charged at once: the card subscription is updated to the new price, '
|
|
474
|
+
+ 'a plan that renews by card shows the difference on its next invoice, and the added credits come '
|
|
475
|
+
+ 'for the time left. A running agent restarts once when a skill turns on or off; a change of the '
|
|
476
|
+
+ 'tier alone leaves it running. Newly enabled skills may still need credentials '
|
|
477
|
+
+ '(set_agent_credentials, connect_agent_oauth, bind_agent_project for a Wiro project) and do not create scheduled tasks '
|
|
194
478
|
+ '(manage_agent_custom_skill does). Find skill names with list_agent_skills and the '
|
|
195
479
|
+ 'agent\'s current skills with get_my_agent. The agents playbook (get_playbook) has the '
|
|
196
480
|
+ 'full setup walkthrough.',
|
|
197
|
-
inputSchema: {
|
|
481
|
+
inputSchema: strictInput({
|
|
198
482
|
agent_guid: z.string().min(1).describe('The deployed agent\'s guid, from list_my_agents.'),
|
|
199
483
|
skills: z.record(z.string(), z.boolean())
|
|
200
484
|
.describe('Skills to change, as name: true to turn on or name: false to turn off, for example {"int-instagram-post": true}. Registry names only (list_agent_skills). Custom skills and scheduled tasks (cs-...) are managed with manage_agent_custom_skill; channels such as Telegram turn on through set_agent_credentials.'),
|
|
@@ -205,12 +489,12 @@ export function registerApplyAgentSkills(server, client) {
|
|
|
205
489
|
confirm: z.boolean().default(false)
|
|
206
490
|
.describe('false (default) only returns the quote. true applies the change and charges it; it needs the idempotency_key from the quote.'),
|
|
207
491
|
idempotency_key: z.string().min(8).max(128).optional()
|
|
208
|
-
.describe('With confirm: true, the key from the quote the user approved. It ties the approval to the quoted prices, and repeating the call with it after a timeout never applies the change twice. Omit it to get a quote.'),
|
|
209
|
-
},
|
|
492
|
+
.describe('With confirm: true, the key from the quote the user approved. It ties the approval to the quoted prices and, on a prepaid plan, the billing period, and repeating the call with it after a timeout never applies the change twice. Omit it to get a quote.'),
|
|
493
|
+
}),
|
|
210
494
|
outputSchema,
|
|
211
495
|
annotations: {
|
|
212
496
|
readOnlyHint: false,
|
|
213
|
-
destructiveHint:
|
|
497
|
+
destructiveHint: true,
|
|
214
498
|
idempotentHint: true,
|
|
215
499
|
openWorldHint: true,
|
|
216
500
|
},
|
|
@@ -220,6 +504,7 @@ export function registerApplyAgentSkills(server, client) {
|
|
|
220
504
|
return errorResult(new Error('Nothing to change: pass at least one skill or a tier.'));
|
|
221
505
|
}
|
|
222
506
|
const registry = await loadSkillRegistry(client, extra.signal);
|
|
507
|
+
const titles = registryTitles(registry);
|
|
223
508
|
const plan = planSkillChanges(skills, registry, add_dependencies);
|
|
224
509
|
if (plan.errors.length > 0) {
|
|
225
510
|
return errorResult(new Error(`The change was not sent:\n\n- ${plan.errors.join('\n- ')}`));
|
|
@@ -241,10 +526,13 @@ export function registerApplyAgentSkills(server, client) {
|
|
|
241
526
|
const check = checkSkillSet(enabledNow, diff.changes, registry);
|
|
242
527
|
if (check.deps.length > 0 || check.conflicts.length > 0) {
|
|
243
528
|
const problems = [
|
|
244
|
-
...check.deps.map(dep =>
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
529
|
+
...check.deps.map(dep => {
|
|
530
|
+
const [skill, requires] = [skillLabel(dep.skill, titles), skillLabel(dep.requires, titles)];
|
|
531
|
+
return diff.disable.includes(dep.requires)
|
|
532
|
+
? `${skill} needs ${requires}. Keep ${requires} on, or also turn off ${skill}.`
|
|
533
|
+
: `${skill} needs ${requires}, which is off. Turn ${requires} on in the same change.`;
|
|
534
|
+
}),
|
|
535
|
+
...check.conflicts.map(pair => `${skillLabel(pair.a, titles)} and ${skillLabel(pair.b, titles)} cannot be on together. Keep only one.`),
|
|
248
536
|
];
|
|
249
537
|
return errorResult(new Error(`The change was not sent:\n\n- ${[...new Set(problems)].join('\n- ')}`));
|
|
250
538
|
}
|
|
@@ -257,7 +545,10 @@ export function registerApplyAgentSkills(server, client) {
|
|
|
257
545
|
alreadySet: diff.alreadySet.filter(name => !plan.addedDependencies.includes(name)),
|
|
258
546
|
};
|
|
259
547
|
const credentialsNeeded = credentialsNeededFor(diff.enable, registry, row);
|
|
260
|
-
const
|
|
548
|
+
const serviceTitles = await credentialTitles(client, credentialsNeeded.flatMap(entry => entry.credentials), extra.signal);
|
|
549
|
+
// A running agent restarts only for a skill that turns on or off.
|
|
550
|
+
const skillsMove = Object.keys(diff.changes).length > 0;
|
|
551
|
+
const nothingToDo = !skillsMove && !tierChange;
|
|
261
552
|
const base = {
|
|
262
553
|
agentGuid: agent_guid,
|
|
263
554
|
changes,
|
|
@@ -281,7 +572,8 @@ export function registerApplyAgentSkills(server, client) {
|
|
|
281
572
|
};
|
|
282
573
|
}
|
|
283
574
|
const currentPrice = toNumber(row.monthlypriceusd);
|
|
284
|
-
|
|
575
|
+
// The plan's monthly figure is what each renewal grants. This period's can differ after a change inside it.
|
|
576
|
+
const currentCredits = toNumber(row.renewalcredits) ?? toNumber(row.monthlycredits);
|
|
285
577
|
const key = idempotency_key?.trim() ?? '';
|
|
286
578
|
// Every team member can read a team agent and price a change, but only a
|
|
287
579
|
// team admin or the person who deployed it can apply one. The credit
|
|
@@ -304,29 +596,34 @@ export function registerApplyAgentSkills(server, client) {
|
|
|
304
596
|
isError: true,
|
|
305
597
|
};
|
|
306
598
|
}
|
|
599
|
+
// The tasks Wiro says this change stops: null until it says, and when it could not tell.
|
|
600
|
+
let stops = null;
|
|
307
601
|
if (!nothingToDo) {
|
|
308
602
|
const preview = requireApiSuccess(await client.post('/UserAgent/PricingPreview', {
|
|
309
603
|
useragentguid: agent_guid,
|
|
310
604
|
skillOverrides: diff.changes,
|
|
311
605
|
...(tierChange ? { tier: tierChange } : {}),
|
|
606
|
+
withScheduledTasks: true,
|
|
312
607
|
}, extra.signal), 'Could not price this change.');
|
|
608
|
+
const moved = scheduledTasksOf(preview.scheduledTasks);
|
|
609
|
+
stops = moved === null ? null : moved?.stop ?? [];
|
|
313
610
|
const newPrice = toNumber(preview.totalPriceUsd);
|
|
314
611
|
if (newPrice === null) {
|
|
315
612
|
throw new Error('Wiro did not return the new monthly price of this change, so it cannot be quoted. Try again later.');
|
|
316
613
|
}
|
|
317
|
-
const priceTag = skillPriceTag(currentPrice, newPrice);
|
|
614
|
+
const priceTag = skillPriceTag(currentPrice, newPrice, billing.plan === 'prepaid' ? billing.periodEnd : null);
|
|
318
615
|
const approved = confirm && givenKey !== null && givenKey.priceTag === priceTag;
|
|
319
616
|
if (!approved) {
|
|
320
617
|
const newCredits = toNumber(preview.totalMonthlyCredits);
|
|
321
618
|
const delta = difference(newPrice, currentPrice);
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
const
|
|
619
|
+
// What Wiro works out for this change on the plan as it is now. Only an active plan has one.
|
|
620
|
+
const change = billing.plan !== 'none' && billing.status === 'active' ? readPlanChange(preview.planChange) : null;
|
|
621
|
+
const note = quoteBillingNote(billing, delta, change, newPrice, changes.disable.length);
|
|
622
|
+
const walletDrop = change !== null && change.wallet && delta !== null && delta < 0;
|
|
326
623
|
const quoteKey = givenKey?.priceTag === priceTag ? key : newSkillChangeKey(digest, priceTag);
|
|
327
624
|
const priceChanged = confirm && givenKey !== null;
|
|
328
625
|
const blocked = billing.plan !== 'none' && billing.status !== 'active';
|
|
329
|
-
const restart = restartNote(status, 'skill change');
|
|
626
|
+
const restart = skillsMove ? restartNote(status, 'skill change') : TIER_ONLY_RESTART;
|
|
330
627
|
const priceNote = todayPriceNote(row, billing, currentPrice, currentCredits, tierChange);
|
|
331
628
|
const pricing = {
|
|
332
629
|
currentMonthlyUsd: currentPrice,
|
|
@@ -335,12 +632,13 @@ export function registerApplyAgentSkills(server, client) {
|
|
|
335
632
|
currentMonthlyCredits: currentCredits,
|
|
336
633
|
newMonthlyCredits: newCredits,
|
|
337
634
|
deltaMonthlyCredits: difference(newCredits, currentCredits),
|
|
635
|
+
...periodFields(change),
|
|
338
636
|
...(priceNote ? { note: priceNote } : {}),
|
|
339
637
|
};
|
|
340
638
|
const nextAction = blocked
|
|
341
639
|
? {
|
|
342
640
|
tool: 'manage_agent_billing',
|
|
343
|
-
arguments: { agent_guid, action: 'renew' },
|
|
641
|
+
arguments: { agent_guid, action: renewsBack(billing) ? 'renew' : 'subscribe' },
|
|
344
642
|
reason: 'The subscription must be active before skills can change.',
|
|
345
643
|
}
|
|
346
644
|
: {
|
|
@@ -360,29 +658,35 @@ export function registerApplyAgentSkills(server, client) {
|
|
|
360
658
|
const lead = !confirm
|
|
361
659
|
? null
|
|
362
660
|
: priceChanged
|
|
363
|
-
? 'The price changed since the quote, so nothing was applied or charged. This is the new quote with a new idempotency_key; show it to the user and ask again.'
|
|
661
|
+
? 'The price or the billing period changed since the quote, so nothing was applied or charged. This is the new quote with a new idempotency_key; show it to the user and ask again.'
|
|
364
662
|
: 'Nothing was applied: confirming needs the idempotency_key of a quote, which ties the approval to the price. This is the quote; show it to the user first.';
|
|
365
663
|
const lines = [
|
|
366
664
|
'## Skill change quote',
|
|
367
665
|
'',
|
|
368
666
|
...(lead ? [lead, ''] : []),
|
|
369
667
|
`**Agent:** \`${agent_guid}\` (${agentStatusLabel(status)})`,
|
|
370
|
-
listLine('Turn on', changes.enable),
|
|
371
|
-
listLine('Added because other skills need them', changes.addedDependencies),
|
|
372
|
-
listLine('Turn off', changes.disable),
|
|
373
|
-
listLine('Already as requested', changes.alreadySet),
|
|
668
|
+
listLine('Turn on', changes.enable, titles),
|
|
669
|
+
listLine('Added because other skills need them', changes.addedDependencies, titles),
|
|
670
|
+
listLine('Turn off', changes.disable, titles),
|
|
671
|
+
listLine('Already as requested', changes.alreadySet, titles),
|
|
374
672
|
tierChange ? `**Plan:** ${currentTier ?? 'unknown'} to ${tierChange}` : null,
|
|
375
673
|
`**Monthly price:** ${currentPrice !== null ? formatUsd(currentPrice) : 'unknown'} now, ${formatUsd(newPrice)} after the change${delta !== null ? ` (${delta >= 0 ? '+' : ''}${formatUsd(delta)})` : ''}`,
|
|
376
674
|
priceNote ? `**Price update:** ${priceNote}` : null,
|
|
377
|
-
|
|
675
|
+
change?.creditsFromRenewal !== null && change?.creditsFromRenewal !== undefined
|
|
676
|
+
? `**Monthly credits:** ${currentCredits !== null ? formatCredits(currentCredits) : 'unknown'} now, ${formatCredits(change.creditsFromRenewal)} ${renewalWhen(billing)}`
|
|
677
|
+
: newCredits !== null ? `**Monthly credits:** ${currentCredits !== null ? formatCredits(currentCredits) : 'unknown'} now, ${formatCredits(newCredits)} ${creditsWhen(billing)}` : null,
|
|
678
|
+
change && change.creditsNow !== null && change.creditsThisPeriod !== null
|
|
679
|
+
? `**This period:** ${signedCredits(change.creditsNow)} credits now, ${formatCredits(change.creditsThisPeriod)} in all until ${unixToIso(change.periodEnd) ?? 'the period ends'}`
|
|
680
|
+
: null,
|
|
378
681
|
`**Billing:** ${note}`,
|
|
379
682
|
`**Restart:** ${restart}`,
|
|
683
|
+
...movedTaskLines(moved, row, running),
|
|
380
684
|
credentialsNeeded.length > 0
|
|
381
|
-
? `**Credentials the new skills need:** ${credentialsNeeded
|
|
685
|
+
? `**Credentials the new skills need:** ${neededText(credentialsNeeded, row, titles, serviceTitles)}.${bindProjectNote(credentialsNeeded)}`
|
|
382
686
|
: null,
|
|
383
687
|
'',
|
|
384
688
|
blocked
|
|
385
|
-
? 'Renew the subscription with manage_agent_billing before applying this change
|
|
689
|
+
? `${renewsBack(billing) ? 'Renew the subscription' : 'Start a new plan'} with manage_agent_billing before applying this change.`
|
|
386
690
|
: `Nothing has changed yet. After the user approves, call apply_agent_skills again with confirm: true and idempotency_key \`${quoteKey}\`.`,
|
|
387
691
|
].filter((line) => line !== null);
|
|
388
692
|
return {
|
|
@@ -393,8 +697,14 @@ export function registerApplyAgentSkills(server, client) {
|
|
|
393
697
|
idempotencyKey: quoteKey,
|
|
394
698
|
...(priceChanged ? { priceChanged: true } : {}),
|
|
395
699
|
pricing,
|
|
396
|
-
billing: {
|
|
700
|
+
billing: {
|
|
701
|
+
...billingBase,
|
|
702
|
+
estimatedChargeNowUsd: change ? change.chargeNowUsd : null,
|
|
703
|
+
...(walletDrop ? { creditNowUsd: change.creditNowUsd, usedCreditsUsd: change.usedCreditsUsd } : {}),
|
|
704
|
+
note,
|
|
705
|
+
},
|
|
397
706
|
...(Array.isArray(preview.enabledSkills) ? { enabledSkills: preview.enabledSkills.map(String) } : {}),
|
|
707
|
+
...(moved !== undefined ? { scheduledTasks: moved } : {}),
|
|
398
708
|
restart,
|
|
399
709
|
nextAction,
|
|
400
710
|
},
|
|
@@ -415,9 +725,16 @@ export function registerApplyAgentSkills(server, client) {
|
|
|
415
725
|
if (extra.signal.aborted)
|
|
416
726
|
throw error;
|
|
417
727
|
if (!outcomeUnknown(error)) {
|
|
418
|
-
const failure = describeSkillsApplyError(error, billing.plan);
|
|
728
|
+
const failure = describeSkillsApplyError(error, billing.plan, titles);
|
|
729
|
+
// Code 110 is the whole outcome: the next step reads the agent, and the call is not repeated.
|
|
730
|
+
const failedChange = failure.code === 110;
|
|
731
|
+
const nextAction = failedChange
|
|
732
|
+
? { tool: 'get_my_agent', arguments: { agent_guid }, reason: 'Check the agent\'s skills and monthly price. Do not repeat the change.' }
|
|
733
|
+
: failure.code === 107
|
|
734
|
+
? { tool: 'apply_agent_skills', arguments: { agent_guid, skills, ...(tier ? { tier } : {}), add_dependencies }, reason: 'Get a new quote for this change, with a new key.' }
|
|
735
|
+
: undefined;
|
|
419
736
|
const text = [
|
|
420
|
-
'## Skill change not applied',
|
|
737
|
+
failedChange ? '## Skill change failed' : '## Skill change not applied',
|
|
421
738
|
'',
|
|
422
739
|
failure.message,
|
|
423
740
|
failure.guidance ? `\n${failure.guidance}` : '',
|
|
@@ -430,7 +747,7 @@ export function registerApplyAgentSkills(server, client) {
|
|
|
430
747
|
status: 'refused',
|
|
431
748
|
idempotencyKey: key,
|
|
432
749
|
billing: { ...billingBase, note: failure.guidance || failure.message },
|
|
433
|
-
restart: UNCHANGED_RESTART,
|
|
750
|
+
restart: failedChange ? 'The agent does not restart for this change.' : UNCHANGED_RESTART,
|
|
434
751
|
error: {
|
|
435
752
|
code: failure.code,
|
|
436
753
|
message: failure.message,
|
|
@@ -439,6 +756,7 @@ export function registerApplyAgentSkills(server, client) {
|
|
|
439
756
|
...(failure.requiredUsd !== undefined ? { requiredUsd: failure.requiredUsd } : {}),
|
|
440
757
|
...(failure.availableUsd !== undefined ? { availableUsd: failure.availableUsd } : {}),
|
|
441
758
|
},
|
|
759
|
+
...(nextAction ? { nextAction } : {}),
|
|
442
760
|
},
|
|
443
761
|
isError: true,
|
|
444
762
|
};
|
|
@@ -446,7 +764,9 @@ export function registerApplyAgentSkills(server, client) {
|
|
|
446
764
|
const message = errorText(error);
|
|
447
765
|
if (sentEmpty) {
|
|
448
766
|
const note = 'The agent\'s skills already match the request, so the change is in place. Do not repeat it. '
|
|
449
|
-
+
|
|
767
|
+
+ (billing.plan === 'prepaid'
|
|
768
|
+
? `What the earlier call with this idempotency_key took from, or credited to, ${walletName(billing)} is not shown here; get_account with recent_transactions shows it.`
|
|
769
|
+
: 'Any price difference follows the plan\'s usual billing.');
|
|
450
770
|
return {
|
|
451
771
|
content: [{ type: 'text', text: `## No change needed\n\n${note}` }],
|
|
452
772
|
structuredContent: {
|
|
@@ -460,25 +780,28 @@ export function registerApplyAgentSkills(server, client) {
|
|
|
460
780
|
}
|
|
461
781
|
// The agent is read again to see whether the change is in place
|
|
462
782
|
// before anything is reported.
|
|
463
|
-
const holds = await
|
|
783
|
+
const { holds, live } = await readAfterChange(client, agent_guid, plan.requested, tierChange, extra.signal);
|
|
464
784
|
if (holds === true) {
|
|
465
|
-
const
|
|
785
|
+
const paused = newlyPaused(row, live, stops);
|
|
786
|
+
const note = `The agent's skills now match the request, so the change is in place. Do not repeat it. ${billing.plan === 'prepaid' && billing.status === 'active' ? `The difference for the rest of this period was probably taken from, or credited to, ${walletName(billing)}; a credit goes back to the balances that paid. get_account with recent_transactions shows the amount, and get_agent_activity with kind usage the credits it moved.` : 'Any price difference follows the plan\'s usual billing.'}`;
|
|
466
787
|
return {
|
|
467
788
|
content: [{ type: 'text', text: [
|
|
468
789
|
'## Skills updated',
|
|
469
790
|
'',
|
|
470
791
|
`**Agent:** \`${agent_guid}\``,
|
|
471
|
-
listLine('Turned on', changes.enable),
|
|
472
|
-
listLine('Turned off', changes.disable),
|
|
792
|
+
listLine('Turned on', changes.enable, titles),
|
|
793
|
+
listLine('Turned off', changes.disable, titles),
|
|
473
794
|
`**Billing:** ${note}`,
|
|
474
|
-
`**Restart:** ${restartNote(status, 'skill change')}`,
|
|
795
|
+
`**Restart:** ${skillsMove ? restartNote(status, 'skill change') : TIER_ONLY_RESTART}`,
|
|
796
|
+
await pausedLine(client, paused, extra.signal),
|
|
475
797
|
].filter((line) => line !== null).join('\n') }],
|
|
476
798
|
structuredContent: {
|
|
477
799
|
...base,
|
|
478
800
|
status: 'applied',
|
|
479
801
|
idempotencyKey: key,
|
|
480
802
|
billing: { ...billingBase, chargedNowUsd: null, note },
|
|
481
|
-
restart: restartNote(status, 'skill change'),
|
|
803
|
+
restart: skillsMove ? restartNote(status, 'skill change') : TIER_ONLY_RESTART,
|
|
804
|
+
...(paused.length > 0 ? { pausedTasks: paused.map(entry => entry.key) } : {}),
|
|
482
805
|
},
|
|
483
806
|
};
|
|
484
807
|
}
|
|
@@ -493,7 +816,7 @@ export function registerApplyAgentSkills(server, client) {
|
|
|
493
816
|
status: 'refused',
|
|
494
817
|
idempotencyKey: key,
|
|
495
818
|
billing: { ...billingBase, note },
|
|
496
|
-
restart: unverified ? 'The agent restarts if the change was saved.' :
|
|
819
|
+
restart: !unverified ? UNCHANGED_RESTART : skillsMove ? 'The agent restarts if the change was saved.' : TIER_ONLY_RESTART,
|
|
497
820
|
error: { code: null, message },
|
|
498
821
|
},
|
|
499
822
|
isError: true,
|
|
@@ -501,7 +824,7 @@ export function registerApplyAgentSkills(server, client) {
|
|
|
501
824
|
}
|
|
502
825
|
// The live agent is checked against this change before it is reported
|
|
503
826
|
// as applied.
|
|
504
|
-
const holds = await
|
|
827
|
+
const { holds, live } = await readAfterChange(client, agent_guid, plan.requested, tierChange, extra.signal);
|
|
505
828
|
if (holds === false) {
|
|
506
829
|
const note = 'The agent\'s skills do not match this change, so it is not in place. Check get_my_agent; if they '
|
|
507
830
|
+ 'still do not match the request, call apply_agent_skills again without idempotency_key to get a new quote.';
|
|
@@ -527,6 +850,13 @@ export function registerApplyAgentSkills(server, client) {
|
|
|
527
850
|
// result of the earlier call with this key, not a new charge.
|
|
528
851
|
const replayed = sentEmpty && !applied.noOp;
|
|
529
852
|
const serverPricing = applied.pricing ?? {};
|
|
853
|
+
// An agent with no plan has no period and no renewal yet, as its quote says.
|
|
854
|
+
const appliedChange = billing.plan === 'none' ? null : readPlanChange({
|
|
855
|
+
creditsNow: serverPricing.creditsNow,
|
|
856
|
+
creditsThisPeriod: serverPricing.creditsThisPeriod,
|
|
857
|
+
creditsFromRenewal: serverPricing.creditsFromRenewal,
|
|
858
|
+
periodEnd: serverPricing.periodEnd,
|
|
859
|
+
});
|
|
530
860
|
const pricing = applied.noOp
|
|
531
861
|
? undefined
|
|
532
862
|
: {
|
|
@@ -536,46 +866,58 @@ export function registerApplyAgentSkills(server, client) {
|
|
|
536
866
|
currentMonthlyCredits: toNumber(serverPricing.previousMonthlyCredits),
|
|
537
867
|
newMonthlyCredits: toNumber(serverPricing.newMonthlyCredits),
|
|
538
868
|
deltaMonthlyCredits: toNumber(serverPricing.deltaCredits),
|
|
869
|
+
...periodFields(appliedChange),
|
|
539
870
|
};
|
|
540
871
|
const charge = appliedBillingNote(billing, applied);
|
|
872
|
+
const usedCreditsUsd = toNumber(applied.usedCreditsUsd);
|
|
541
873
|
const billingNote = replayed
|
|
542
874
|
? `This change was already applied by an earlier call with this idempotency_key, and nothing new was charged now. That call's billing: ${charge.note}`
|
|
543
875
|
: charge.note;
|
|
876
|
+
// Wiro says whether this change restarts a running agent; an answer without it goes by the skills that move.
|
|
877
|
+
const restarts = typeof applied.restartTriggered === 'boolean' ? applied.restartTriggered : skillsMove;
|
|
544
878
|
const restart = applied.noOp || replayed
|
|
545
879
|
? UNCHANGED_RESTART
|
|
546
|
-
: restartNote(status, 'skill change');
|
|
880
|
+
: restarts ? restartNote(status, 'skill change') : TIER_ONLY_RESTART;
|
|
547
881
|
const firstCredential = credentialsNeeded[0]?.credentials[0];
|
|
548
882
|
const nextAction = applied.noOp || replayed
|
|
549
883
|
? undefined
|
|
550
|
-
: firstCredential
|
|
551
|
-
?
|
|
552
|
-
|
|
553
|
-
arguments: { agent_guid, credential: firstCredential },
|
|
554
|
-
reason: `See what ${firstCredential} needs before asking the user for it.`,
|
|
555
|
-
}
|
|
556
|
-
: running
|
|
884
|
+
: firstCredential === WIRO_PROJECT_CREDENTIAL
|
|
885
|
+
? bindProjectStep(agent_guid, 'Connect the Wiro project the new skills need.')
|
|
886
|
+
: firstCredential
|
|
557
887
|
? {
|
|
558
|
-
tool: '
|
|
559
|
-
arguments: { agent_guid,
|
|
560
|
-
reason:
|
|
888
|
+
tool: 'set_agent_credentials',
|
|
889
|
+
arguments: { agent_guid, credential: firstCredential },
|
|
890
|
+
reason: `See what ${firstCredential} needs before asking the user for it.`,
|
|
561
891
|
}
|
|
562
|
-
:
|
|
892
|
+
: running && restarts
|
|
893
|
+
? {
|
|
894
|
+
tool: 'get_my_agent',
|
|
895
|
+
arguments: { agent_guid, wait_seconds: DEFAULT_WAIT_SECONDS },
|
|
896
|
+
reason: 'Check that the agent is running again with the new skills.',
|
|
897
|
+
}
|
|
898
|
+
: undefined;
|
|
899
|
+
// Read from the agent after the change: the tasks it paused, which the quote named among those that stop.
|
|
900
|
+
const paused = applied.noOp || replayed ? [] : newlyPaused(row, live, stops);
|
|
563
901
|
const lines = [
|
|
564
902
|
applied.noOp ? '## No change needed' : replayed ? '## Already applied' : '## Skills updated',
|
|
565
903
|
'',
|
|
566
904
|
`**Agent:** \`${agent_guid}\``,
|
|
567
|
-
listLine('Turned on', changes.enable),
|
|
568
|
-
listLine('Turned off', changes.disable),
|
|
569
|
-
applied.noOp || replayed ? listLine('Already as requested', changes.alreadySet) : null,
|
|
905
|
+
listLine('Turned on', changes.enable, titles),
|
|
906
|
+
listLine('Turned off', changes.disable, titles),
|
|
907
|
+
applied.noOp || replayed ? listLine('Already as requested', changes.alreadySet, titles) : null,
|
|
570
908
|
tierChange && !applied.noOp ? `**Plan:** now ${asString(applied.tier) ?? tierChange}` : null,
|
|
571
909
|
pricing?.newMonthlyUsd !== undefined && pricing.newMonthlyUsd !== null
|
|
572
910
|
? `**Monthly price:** ${formatUsd(pricing.newMonthlyUsd)}${pricing.currentMonthlyUsd !== null ? ` (was ${formatUsd(pricing.currentMonthlyUsd)})` : ''}`
|
|
573
911
|
: null,
|
|
912
|
+
pricing && appliedChange?.creditsNow !== null && appliedChange?.creditsNow !== undefined && appliedChange.creditsThisPeriod !== null
|
|
913
|
+
? `**This period:** ${signedCredits(appliedChange.creditsNow)} credits, ${formatCredits(appliedChange.creditsThisPeriod)} in all until ${unixToIso(appliedChange.periodEnd) ?? 'the period ends'}${appliedChange.creditsFromRenewal !== null ? `; ${renewalGives(billing, appliedChange.creditsFromRenewal)}` : ''}`
|
|
914
|
+
: null,
|
|
574
915
|
`**Billing:** ${billingNote}`,
|
|
575
916
|
`**Restart:** ${restart}`,
|
|
576
917
|
holds === null ? 'The agent could not be read afterwards to confirm the new skills; get_my_agent shows them.' : null,
|
|
918
|
+
await pausedLine(client, paused, extra.signal),
|
|
577
919
|
credentialsNeeded.length > 0
|
|
578
|
-
? `**Still needed:** credentials for ${credentialsNeeded
|
|
920
|
+
? `**Still needed:** credentials for ${neededText(credentialsNeeded, row, titles, serviceTitles)}. ${stillNeededSteps(credentialsNeeded)}`
|
|
579
921
|
: null,
|
|
580
922
|
].filter((line) => line !== null);
|
|
581
923
|
return {
|
|
@@ -585,9 +927,16 @@ export function registerApplyAgentSkills(server, client) {
|
|
|
585
927
|
status: applied.noOp ? 'no_change' : 'applied',
|
|
586
928
|
idempotencyKey: key,
|
|
587
929
|
...(pricing ? { pricing } : {}),
|
|
588
|
-
|
|
930
|
+
// The saved answer of an earlier call: its charge is in the note, and this call charged nothing.
|
|
931
|
+
billing: {
|
|
932
|
+
...billingBase,
|
|
933
|
+
chargedNowUsd: replayed ? 0 : charge.charged,
|
|
934
|
+
...(usedCreditsUsd !== null && usedCreditsUsd > 0 ? { usedCreditsUsd } : {}),
|
|
935
|
+
note: billingNote,
|
|
936
|
+
},
|
|
589
937
|
...(Array.isArray(serverPricing.enabledSkills) ? { enabledSkills: serverPricing.enabledSkills.map(String) } : {}),
|
|
590
938
|
restart,
|
|
939
|
+
...(paused.length > 0 ? { pausedTasks: paused.map(entry => entry.key) } : {}),
|
|
591
940
|
...(nextAction ? { nextAction } : {}),
|
|
592
941
|
},
|
|
593
942
|
};
|