@workerkit/core 0.2.1 → 0.3.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/CHANGELOG.md +110 -0
- package/README.md +2 -2
- package/dist/descriptors/directory.d.ts.map +1 -1
- package/dist/descriptors/directory.js +76 -4
- package/dist/descriptors/directory.js.map +1 -1
- package/dist/descriptors/index.d.ts +1 -1
- package/dist/descriptors/index.js +1 -1
- package/dist/descriptors/index.js.map +1 -1
- package/dist/descriptors/manage.d.ts.map +1 -1
- package/dist/descriptors/manage.js +617 -4
- package/dist/descriptors/manage.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// The
|
|
1
|
+
// The 74 authenticated fleet-management tools, as pure descriptors. The
|
|
2
2
|
// descriptions ARE the product surface: they are served verbatim to MCP
|
|
3
3
|
// clients (and any future CLI help), so every contract nuance an agent must
|
|
4
4
|
// not get wrong is taught here.
|
|
@@ -37,7 +37,7 @@ const getKeyInfo = {
|
|
|
37
37
|
const listWorkers = {
|
|
38
38
|
name: "workers_list",
|
|
39
39
|
title: "List Workers",
|
|
40
|
-
description: "List every AI worker on this account with live status: title, avatar, status (active|paused|expired), isEnabled, expiresAt, lastRun (the newest SETTLED run — in-flight runs never appear here), schedule rollup (total/enabled/nextRunUtc),
|
|
40
|
+
description: "List every AI worker on this account with live status: title, avatar, status (active|paused|expired), isEnabled, expiresAt, lastRun (the newest SETTLED run — in-flight runs never appear here), schedule rollup (total/enabled/nextRunUtc), live state (isRunning, inFlightRuns, currentRunId — poll run_get with currentRunId to watch it), and deployment (null = this worker is NOT on the hosted runtime and will never run; worker_deploy fixes that). Compute elapsed/next-run times against the response's serverTimeUtc, never your own clock. IMPORTANT: lastRun.resultText is the report the worker itself wrote and requires the manager key to hold the readRuns scope IN ADDITION to readWorkers — with readWorkers only it is null, which does NOT mean the run produced no report; check your key's scopes before concluding anything from a null. Scope model for every tool here: a 403 OPERATION_NOT_ALLOWED names the missing scope — the key must be re-minted with broader scopes by an account admin at https://workerkit.ai (do not retry the call).",
|
|
41
41
|
auth: "manager",
|
|
42
42
|
method: "get",
|
|
43
43
|
schema: {},
|
|
@@ -47,7 +47,7 @@ const listWorkers = {
|
|
|
47
47
|
const getWorker = {
|
|
48
48
|
name: "worker_get",
|
|
49
49
|
title: "Get Worker",
|
|
50
|
-
description: "One worker in full: everything workers_list shows plus timeZoneId, maxRunsPerDay, kit provenance (templateId/slug/name), hasInstruction, isProtected, jobSentence, readiness (status ready|blocked with actionable issues — worker_inactive, no_identity, app_not_connected incl. candidateProviders, no_instruction), and 30-day activity (runs by outcome, success rate, cost; null while hosted runs are not enabled for the environment).",
|
|
50
|
+
description: "One worker in full: everything workers_list shows plus timeZoneId, maxRunsPerDay, kit provenance (templateId/slug/name), hasInstruction, isProtected, jobSentence, readiness (status ready|blocked with actionable issues — worker_inactive, no_identity, app_not_connected incl. candidateProviders, no_instruction), apps[] (every app the worker has enabled with connection connected|needs_connection|unknown and the providers serving it — the per-worker view of apps_list; an app at needs_connection is fixed with app_connect on the worker's operator or on the dashboard), deployment (status + modelSlug, or NULL WHEN THE WORKER IS NOT DEPLOYED — an undeployed worker never runs, whatever readiness says, because readiness judges configuration and deployment is what puts it on the runtime; fix with worker_deploy), and 30-day activity (runs by outcome, success rate, cost; null while hosted runs are not enabled for the environment).",
|
|
51
51
|
auth: "manager",
|
|
52
52
|
method: "get",
|
|
53
53
|
schema: {
|
|
@@ -682,7 +682,7 @@ const kitInstallPreview = {
|
|
|
682
682
|
const kitInstall = {
|
|
683
683
|
name: "kit_install",
|
|
684
684
|
title: "Install Kit",
|
|
685
|
-
description: "Install a directory kit as a NEW worker on this account. NOT idempotent: every successful call creates another worker — never retry a success, and on a timeout check workers_list (needs the readWorkers scope) before trying again. Requires the installKits scope. Call kit_install_preview first and supply every requiredInputs key in inputs (a missing or unknown key is a 400 naming it), answers to required memorySetup questions in memoryAnswers, and one categoryChoices entry per slot (member with connectionProvider set → also pass resourceId from the preview's operatorResources). CRITICAL — secrets shown ONCE: the response's install.rawKey (the worker's pe_ API key) and install.triggers[].signingSecret can NEVER be read again; deliver them to the human immediately and do not discard the response before doing so. The response also carries readiness (status ready|blocked with actionable issues, e.g. app_not_connected + candidateProviders; null means the readiness check itself failed AFTER the install succeeded — do not retry the install, read readiness via worker_get), workerUrl (the worker's dashboard page — hand it to the human), and connectAppsUrl (where the human connects missing apps in the browser; agents cannot connect apps). A 402 {error:'limit_exceeded'} is the plan's worker cap — terminal, do not retry; tell the human to upgrade or free a slot. Rate: 10 installs/hour per account.",
|
|
685
|
+
description: "Install a directory kit as a NEW worker on this account. NOT idempotent: every successful call creates another worker — never retry a success, and on a timeout check workers_list (needs the readWorkers scope) before trying again. Requires the installKits scope. Call kit_install_preview first and supply every requiredInputs key in inputs (a missing or unknown key is a 400 naming it), answers to required memorySetup questions in memoryAnswers, and one categoryChoices entry per slot (member with connectionProvider set → also pass resourceId from the preview's operatorResources). CRITICAL — secrets shown ONCE: the response's install.rawKey (the worker's pe_ API key) and install.triggers[].signingSecret can NEVER be read again; deliver them to the human immediately and do not discard the response before doing so. The response also carries readiness (status ready|blocked with actionable issues, e.g. app_not_connected + candidateProviders; null means the readiness check itself failed AFTER the install succeeded — do not retry the install, read readiness via worker_get), workerUrl (the worker's dashboard page — hand it to the human), and connectAppsUrl (where the human connects missing apps in the browser; agents cannot connect apps). A 402 {error:'limit_exceeded'} is the plan's worker cap — terminal, do not retry; tell the human to upgrade or free a slot. Rate: 10 installs/hour per account. FINISHING THE JOB — installing does NOT make the worker run: without a deployment it fires no schedule and worker_run refuses it with not_deployed. Send deploy:true (optionally with modelSlug from models_list and the ceilings) to install and deploy in ONE call; the response then carries deployment. A deploy refused after the install still returns 201 with deploymentError naming what to fix — the worker exists either way, so never re-install; fix it and call worker_deploy on the same worker. deploy:true needs the manageDeployments scope IN ADDITION to installKits — a key without it is refused up front (403 OPERATION_NOT_ALLOWED naming the scope) and nothing is created, so drop deploy and install anyway, then ask the owner to re-scope the key before worker_deploy.",
|
|
686
686
|
auth: "manager",
|
|
687
687
|
method: "post",
|
|
688
688
|
schema: {
|
|
@@ -697,6 +697,14 @@ const kitInstall = {
|
|
|
697
697
|
})).optional().describe("One entry per categorySlots slot. Omitted slots resolve to the kit's first offered member on safe defaults."),
|
|
698
698
|
inputs: z.record(z.string(), z.string()).optional().describe("Answers to requiredInputs, keyed EXACTLY by each entry's key. Every key is mandatory; values are single-line, 1-200 chars."),
|
|
699
699
|
memoryAnswers: z.record(z.string(), z.string()).optional().describe("Answers to memorySetup questions by key. Entries with required:true are mandatory. Keep this separate from inputs — mixing the two maps is a 400."),
|
|
700
|
+
deploy: z.boolean().optional().describe("true = also put the new worker on the hosted runtime, so it actually runs. Without this the install creates a worker that fires no schedule and refuses worker_run with not_deployed. Needs the manageDeployments scope."),
|
|
701
|
+
deployment: z.object({
|
|
702
|
+
modelSlug: z.string().max(64).optional().describe("Model to deploy on, from models_list. Omit for the kit's recommended model."),
|
|
703
|
+
maxUsdPerRun: z.number().min(0.01).max(1000).optional().describe("Hard per-run ceiling in USD. Omit for the platform default."),
|
|
704
|
+
maxUsdPerDay: z.number().min(0.01).max(10000).optional().describe("Per-day reserved-spend ceiling, at least maxUsdPerRun. Omit for the platform default."),
|
|
705
|
+
thinking: z.string().max(16).optional().describe("Reasoning setting for the chosen model — see worker_deploy. Omit for 'default'."),
|
|
706
|
+
transcriptRetention: z.string().max(16).optional().describe("Transcript setting. Omit for the platform default."),
|
|
707
|
+
}).optional().describe("Deployment settings, all optional. Sending this implies deploy:true — settings for a step you did not want make no sense."),
|
|
700
708
|
},
|
|
701
709
|
path: (params) => `${KITS_API}/${encodeURIComponent(String(params.slug))}/install`,
|
|
702
710
|
bodyBuilder: (params) => ({
|
|
@@ -706,6 +714,8 @@ const kitInstall = {
|
|
|
706
714
|
categoryChoices: params.categoryChoices,
|
|
707
715
|
inputs: params.inputs,
|
|
708
716
|
memoryAnswers: params.memoryAnswers,
|
|
717
|
+
deploy: params.deploy,
|
|
718
|
+
deployment: params.deployment,
|
|
709
719
|
}),
|
|
710
720
|
annotations: CREATE,
|
|
711
721
|
};
|
|
@@ -876,6 +886,578 @@ const setFleetBudget = {
|
|
|
876
886
|
}),
|
|
877
887
|
annotations: UPDATE,
|
|
878
888
|
};
|
|
889
|
+
// ─── Deployment (the hosted runtime) ────────────────────────────────────────
|
|
890
|
+
// The step between "a worker exists" and "a worker runs". Installing a kit or
|
|
891
|
+
// cloning without the deployment flag leaves a worker that is fully configured
|
|
892
|
+
// and completely inert: no schedule fires and worker_run answers 409
|
|
893
|
+
// not_deployed. Deploying is also where the model and the spend ceilings are
|
|
894
|
+
// chosen, which is why the writes sit behind their own scope.
|
|
895
|
+
const DEPLOYMENT_NOTE = " A worker with NO DEPLOYMENT is configuration only: its schedules never fire and worker_run refuses it with 409 not_deployed. Deploying is what puts it on the runtime, and it is also where the model and the per-run / per-day spend ceilings are set. Reading a deployment rides on readWorkers; every write here needs manageDeployments.";
|
|
896
|
+
const listModels = {
|
|
897
|
+
name: "models_list",
|
|
898
|
+
title: "List Deployable Models",
|
|
899
|
+
description: "The models this ACCOUNT may deploy a worker on, priced per million tokens — the picker for worker_deploy's modelSlug. Each row carries slug (the value deployment calls take), displayName, provider, inputUsdPerMTok / outputUsdPerMTok / cachedInputUsdPerMTok / cacheWriteInputUsdPerMTok, contextWindowK, minTier, recommended, and the reasoning vocabulary. A MODEL ABSENT FROM THIS LIST IS NOT DEPLOYABLE HERE — either the account's tier does not reach it or no live provider serves it — so never pass a slug you read somewhere else; that is a 400 one call later. reasoningStyle says what the deployment's thinking field accepts FOR THAT MODEL: 'budget' takes a per-turn token count AS A STRING ('1024', between thinkingBudgetMin and thinkingBudgetMax), 'effort' takes one of reasoningEffortOptions ('high'), 'none' takes only 'default' or 'off'. Sending the wrong kind is 400 invalid_thinking. byokProviders lists the providers this account holds its own API key for (model_keys_list) — a run on a model from one of those bills the account's key plus a platform fee instead of the wallet. Pass kitSlug to have the kit's own recommendation marked recommended:true; without it nothing is marked. Requires readWorkers." +
|
|
900
|
+
RUNTIME_NOTE,
|
|
901
|
+
auth: "manager",
|
|
902
|
+
method: "get",
|
|
903
|
+
schema: {
|
|
904
|
+
kitSlug: z.string().max(120).optional().describe("Kit slug whose recommended model should be marked recommended:true — typically the kit you are about to install or just installed. Omit when no kit is involved."),
|
|
905
|
+
},
|
|
906
|
+
path: `${API}/models`,
|
|
907
|
+
annotations: READ_ONLY,
|
|
908
|
+
};
|
|
909
|
+
const listDeployments = {
|
|
910
|
+
name: "deployments_list",
|
|
911
|
+
title: "List Deployments",
|
|
912
|
+
description: "Every DEPLOYED worker on the account with its model, spend ceilings and deployment status — the fleet answer to \"what is actually able to run\". A worker that appears in workers_list but NOT here is not on the runtime, however complete it looks: that is the single most common reason a newly built worker never produces a run. Account-scoped by the key, so it takes no worker id. Requires readWorkers." +
|
|
913
|
+
RUNTIME_NOTE,
|
|
914
|
+
auth: "manager",
|
|
915
|
+
method: "get",
|
|
916
|
+
schema: {},
|
|
917
|
+
path: `${API}/deployments`,
|
|
918
|
+
annotations: READ_ONLY,
|
|
919
|
+
};
|
|
920
|
+
const getDeployment = {
|
|
921
|
+
name: "deployment_get",
|
|
922
|
+
title: "Get Deployment",
|
|
923
|
+
description: "One worker's deployment: status, modelSlug + modelDisplayName, maxUsdPerRun / maxUsdPerDay, transcriptRetention (+ transcriptRetentionAvailable — false on a worker installed from a protected kit, where the control should not even be offered), thinking, allowPlatformEgress and warnings. 404 not_found means THE WORKER IS NOT DEPLOYED (or is not on this account — the two are indistinguishable by design); the fix is worker_deploy, not a retry. status: Provisioned / Active = it runs; Paused = schedules and run triggers are refused until deployment_update action:'resume'; Suspended = the platform stopped it and only support can lift that. Requires readWorkers." +
|
|
924
|
+
RUNTIME_NOTE,
|
|
925
|
+
auth: "manager",
|
|
926
|
+
method: "get",
|
|
927
|
+
schema: {
|
|
928
|
+
tokenId: z.number().int().min(1).describe(TOKEN_ID_HINT),
|
|
929
|
+
},
|
|
930
|
+
path: (params) => `${API}/${params.tokenId}/deployment`,
|
|
931
|
+
paramFilter: () => ({}),
|
|
932
|
+
annotations: READ_ONLY,
|
|
933
|
+
};
|
|
934
|
+
const deployWorker = {
|
|
935
|
+
name: "worker_deploy",
|
|
936
|
+
title: "Deploy Worker",
|
|
937
|
+
description: "Put the worker on the hosted runtime — the step that makes it runnable and schedulable, and the one an agent most often forgets after kit_install (kit_install's own deploy:true does both in one call). EVERYTHING IS OPTIONAL: send nothing and the worker deploys on the model its kit recommends with the platform's default ceilings; 400 model_required means the kit named none, so pick one from models_list. The refusals are the deploy gauntlet and each names its fix: 422 instruction_required (the worker has no instruction — a hosted run has nothing to execute), 422 apps_not_connected (the message names the apps; fix with app_connect, or drop the app from the worker — a half-connected worker is refused rather than allowed to burn spend on a partial answer), 402 wallet_required (hosted runs meter from the prepaid wallet and AN AGENT CANNOT TOP IT UP — tell the human), 402 hosted_worker_limit, 409 already_deployed (use deployment_update instead), 400 model_unavailable / model_tier (choose another from models_list), 400 invalid_thinking (wrong reasoning vocabulary for the model), 400 invalid_caps (maxUsdPerDay below maxUsdPerRun). warnings[] on success is advisory and never blocks: ip_rules_enforced means the worker's IP allowlist will reject hosted runs, which come from platform egress. Requires manageDeployments." +
|
|
938
|
+
DEPLOYMENT_NOTE +
|
|
939
|
+
RUNTIME_NOTE +
|
|
940
|
+
NEW_SCOPE_NOTE,
|
|
941
|
+
auth: "manager",
|
|
942
|
+
method: "post",
|
|
943
|
+
schema: {
|
|
944
|
+
tokenId: z.number().int().min(1).describe(TOKEN_ID_HINT),
|
|
945
|
+
modelSlug: z.string().max(64).optional().describe("Model to run on, from models_list (its slug field). Omit to take the kit's recommended model — 400 model_required when the kit named none."),
|
|
946
|
+
maxUsdPerRun: z.number().min(0.01).max(1000).optional().describe("Hard per-run ceiling in USD, 0.01-1000 — also the amount reserved from the wallet at dispatch, so it is what a run must be able to AFFORD to start. Omit for the platform default."),
|
|
947
|
+
maxUsdPerDay: z.number().min(0.01).max(10000).optional().describe("Ceiling on this worker's RESERVED spend per day, 0.01-10000. Must be at least maxUsdPerRun. Omit for the platform default."),
|
|
948
|
+
thinking: z.string().max(16).optional().describe("Per-worker reasoning, validated against the CHOSEN MODEL's reasoningStyle (models_list): 'default' (the catalog decides), 'off', a token budget as a string ('1024') for budget-style models, or an effort word ('high') for effort-style models. Omit for 'default'."),
|
|
949
|
+
transcriptRetention: z.string().max(16).optional().describe("Whether full run transcripts are kept. Omit for the platform default; a worker from a protected kit is clamped to 'off' silently, because that transcript would carry the publisher's own instructions."),
|
|
950
|
+
},
|
|
951
|
+
path: (params) => `${API}/${params.tokenId}/deployment`,
|
|
952
|
+
bodyBuilder: (params) => ({
|
|
953
|
+
modelSlug: params.modelSlug,
|
|
954
|
+
maxUsdPerRun: params.maxUsdPerRun,
|
|
955
|
+
maxUsdPerDay: params.maxUsdPerDay,
|
|
956
|
+
thinking: params.thinking,
|
|
957
|
+
transcriptRetention: params.transcriptRetention,
|
|
958
|
+
}),
|
|
959
|
+
annotations: CREATE,
|
|
960
|
+
};
|
|
961
|
+
const updateDeployment = {
|
|
962
|
+
name: "deployment_update",
|
|
963
|
+
title: "Update Deployment",
|
|
964
|
+
description: "Change a live deployment, or pause and resume it. PARTIAL: an OMITTED FIELD MEANS UNCHANGED, so one ceiling can be raised without restating the rest. action:'pause' stops schedules and run triggers while KEEPING the deployment, its model and its ceilings — the reversible way to stop a worker spending, and the one to reach for before worker_undeploy; action:'resume' puts it back, and is refused on a Suspended deployment (only support lifts that). One interplay to know: changing modelSlug WITHOUT sending thinking keeps a still-valid reasoning setting and otherwise clears it to 'default', reporting a thinking_reset warning rather than refusing the model change. 404 not_found = the worker is not deployed; deploy it with worker_deploy first. Requires manageDeployments." +
|
|
965
|
+
DEPLOYMENT_NOTE +
|
|
966
|
+
RUNTIME_NOTE +
|
|
967
|
+
NEW_SCOPE_NOTE,
|
|
968
|
+
auth: "manager",
|
|
969
|
+
method: "patch",
|
|
970
|
+
schema: {
|
|
971
|
+
tokenId: z.number().int().min(1).describe(TOKEN_ID_HINT),
|
|
972
|
+
modelSlug: z.string().max(64).optional().describe("New model, from models_list. Omit to keep. Read the thinking interplay in the description before changing it."),
|
|
973
|
+
maxUsdPerRun: z.number().min(0.01).max(1000).optional().describe("New per-run ceiling in USD, 0.01-1000. Must stay at or below maxUsdPerDay. Omit to keep."),
|
|
974
|
+
maxUsdPerDay: z.number().min(0.01).max(10000).optional().describe("New per-day reserved-spend ceiling, 0.01-10000. Must be at least maxUsdPerRun. Omit to keep."),
|
|
975
|
+
thinking: z.string().max(16).optional().describe("New reasoning setting for the CURRENT model: 'default', 'off', a token budget as a string ('1024'), or an effort word ('high'). Omit to keep."),
|
|
976
|
+
transcriptRetention: z.string().max(16).optional().describe("New transcript setting. Omit to keep."),
|
|
977
|
+
action: z.enum(["pause", "resume"]).optional().describe("'pause' = stop schedules and run triggers, keeping the deployment; 'resume' = start again (refused while Suspended). Omit to change settings only."),
|
|
978
|
+
},
|
|
979
|
+
path: (params) => `${API}/${params.tokenId}/deployment`,
|
|
980
|
+
bodyBuilder: (params) => ({
|
|
981
|
+
modelSlug: params.modelSlug,
|
|
982
|
+
maxUsdPerRun: params.maxUsdPerRun,
|
|
983
|
+
maxUsdPerDay: params.maxUsdPerDay,
|
|
984
|
+
thinking: params.thinking,
|
|
985
|
+
transcriptRetention: params.transcriptRetention,
|
|
986
|
+
action: params.action,
|
|
987
|
+
}),
|
|
988
|
+
annotations: UPDATE,
|
|
989
|
+
};
|
|
990
|
+
const undeployWorker = {
|
|
991
|
+
name: "worker_undeploy",
|
|
992
|
+
title: "Undeploy Worker",
|
|
993
|
+
description: "Take the worker OFF the hosted runtime. The worker itself survives untouched — its permissions, instruction, memory, schedules and its own pe_ API key all remain, and it stays usable over MCP; it simply stops running and stops costing anything, and deployment_get answers 404 afterwards. Deploy it again with worker_deploy. PREFER deployment_update action:'pause' when the intent is 'stop for now': pause keeps the model and the ceilings, while this discards them. 204 on success, 404 when the worker was not deployed. Requires manageDeployments." +
|
|
994
|
+
RUNTIME_NOTE +
|
|
995
|
+
NEW_SCOPE_NOTE,
|
|
996
|
+
auth: "manager",
|
|
997
|
+
method: "delete",
|
|
998
|
+
schema: {
|
|
999
|
+
tokenId: z.number().int().min(1).describe(TOKEN_ID_HINT),
|
|
1000
|
+
},
|
|
1001
|
+
path: (params) => `${API}/${params.tokenId}/deployment`,
|
|
1002
|
+
successMessage: "Worker undeployed. It still exists and keeps its instruction, memory and schedules; deploy it again with worker_deploy to make it run.",
|
|
1003
|
+
annotations: DELETE,
|
|
1004
|
+
};
|
|
1005
|
+
// ─── Permissions (read-only) ────────────────────────────────────────────────
|
|
1006
|
+
const getWorkerPermissions = {
|
|
1007
|
+
name: "worker_permissions_get",
|
|
1008
|
+
title: "Get Worker Permissions",
|
|
1009
|
+
description: "What the worker may touch, in the kit-authoring vocabulary: apps[] ({code, operations, visibleFields, allowAll, writeEnabled, providers, …} — the same shape a kit's content.apps takes), categorySlots[], contactAllowAll, blockedSenderCategories, timeframePastDays / timeframeFutureDays, maxChildTokens, and ruleCounts (per rule table, PRESENCE only — rule values never leave the worker). Read-only: permissions are never written through this surface; they change through a kit (kit_publish private, then kit_install) or in the dashboard. ruleCounts decides between the two ways to multiply a worker: worker_clone carries the rules, kit_publish from sourceWorkerId never does — so a worker with rules would install MORE permissively as a kit than it runs today. Requires the readWorkers scope.",
|
|
1010
|
+
auth: "manager",
|
|
1011
|
+
method: "get",
|
|
1012
|
+
schema: {
|
|
1013
|
+
tokenId: z.number().int().min(1).describe(TOKEN_ID_HINT),
|
|
1014
|
+
},
|
|
1015
|
+
path: (params) => `${API}/${params.tokenId}/permissions`,
|
|
1016
|
+
paramFilter: () => ({}),
|
|
1017
|
+
annotations: READ_ONLY,
|
|
1018
|
+
};
|
|
1019
|
+
// ─── Kits (authoring) ───────────────────────────────────────────────────────
|
|
1020
|
+
//
|
|
1021
|
+
// The publishKits lane. A PRIVATE kit installed with kit_install is how a worker
|
|
1022
|
+
// is created from scratch on this surface (cloning is the only other non-kit
|
|
1023
|
+
// path), so these descriptors teach the flow as much as the wire: read the guide
|
|
1024
|
+
// and the vocabulary on the Directory server, validate, publish private, install.
|
|
1025
|
+
const KIT_REF = z.string().min(1).max(120);
|
|
1026
|
+
const KIT_REF_HINT = "The kit's slug, as returned by my_kits_list / kit_publish (its legacy numeric id is also accepted). Resolution is account-scoped: 404 not_found means no such kit on THIS account — another account's kit is indistinguishable from a nonexistent one.";
|
|
1027
|
+
const AUTHORING_FLOW_NOTE = " Authoring flow: read kit_authoring_guide (index, then schema and rules) and kit_vocabulary on the public WorkerKit Directory server first; kit_validate the exact body you will publish; publish PRIVATE (visibility 'private') and kit_install it; promote with kit_replace (visibility 'public') once a run has proved it. Requires the publishKits scope." +
|
|
1028
|
+
NEW_SCOPE_NOTE;
|
|
1029
|
+
const kitRefParam = KIT_REF.describe(KIT_REF_HINT);
|
|
1030
|
+
const kitRefPath = (params, suffix = "") => `${KITS_API}/${encodeURIComponent(String(params.kitRef))}${suffix}`;
|
|
1031
|
+
const withoutKitRef = (params) => {
|
|
1032
|
+
const { kitRef: _kitRef, ...body } = params;
|
|
1033
|
+
return body;
|
|
1034
|
+
};
|
|
1035
|
+
const LISTING_SCHEMA = {
|
|
1036
|
+
name: z.string().min(1).max(120).describe("Kit name, 1-120 chars — becomes the slug (kebab-cased), immutable once public. Put the searchable noun here."),
|
|
1037
|
+
jobSentence: z.string().min(1).max(200).describe("One-line outcome, 1-200 chars. With name, the ONLY free-text-searched field."),
|
|
1038
|
+
description: z.string().max(20000).optional().describe("Markdown listing description (≤20000; write it under 500: one line, then 3-5 bullets). Not searched."),
|
|
1039
|
+
categorySlugs: z.array(z.string()).min(1).max(8).describe("1-8 browse-category slugs from kit_vocabulary's categories (jobFamilies / roles / industries). An unknown slug fails the publish."),
|
|
1040
|
+
visibility: z.enum(["public", "private"]).optional().describe("'private' (recommended first) = installable only by this account, no supply-chain scan; 'public' (default) = listed in the directory after the fail-closed scan."),
|
|
1041
|
+
isProtected: z.boolean().optional().describe("true = installers get a working worker and the permission manifest but never the instruction text. Independent of visibility."),
|
|
1042
|
+
publisherName: z.string().min(3).max(80).optional().describe("Required on this account's FIRST publish only (creates the publisher profile; globally unique, 409 if taken). Ignored afterwards — rename with publisher_set."),
|
|
1043
|
+
appDescriptions: z.array(z.record(z.unknown())).optional().describe("Per PINNED app: {code, description ≤600, tools:[{key, description ≤300}]} — every key must be an operation the kit grants; describing a slot member is a rejection. Shape: kit_authoring_guide section 'schema'."),
|
|
1044
|
+
recommendedClients: z.array(z.string().max(40)).max(10).optional().describe("≤10 client names, ≤40 chars each, e.g. claude, chatgpt, cursor."),
|
|
1045
|
+
recommendedModel: z.string().max(80).optional().describe("Display-only recommended model name (≤80)."),
|
|
1046
|
+
declaredModelFloor: z.string().max(80).optional().describe("Display-only minimum model (≤80)."),
|
|
1047
|
+
modelScores: z.array(z.record(z.unknown())).max(10).optional().describe("≤10 of {model, score 0-100, notes ≤300} — only for models the kit was actually run on."),
|
|
1048
|
+
};
|
|
1049
|
+
const CONTENT_SCHEMA = z.record(z.unknown()).describe("The kit's content: instructionContent (required), whenToUse, startCommand, endCommand, endCommandDescription, skillResources, appCodes XOR apps, categorySlots, mcpServers, contactAllowAll, blockedSenderCategories, timeframePastDays, timeframeFutureDays, maxChildTokens, memoryProfile, selfFactsEnabled, memorySetup, schedules, triggers, usageWindows. The exact shape, every cap and every rule: kit_authoring_guide section 'schema'; app codes, tool keys and category slugs: kit_vocabulary. Validated strictly server-side — unknown keys are rejected, never ignored — so kit_validate first.");
|
|
1050
|
+
const getMyPublisher = {
|
|
1051
|
+
name: "publisher_get_mine",
|
|
1052
|
+
title: "Get My Publisher",
|
|
1053
|
+
description: "This account's publisher profile — the name every listing publishes under, its slug, description, links, isListed and kitCount. 404 not_found until the account's first publish creates it: then that first kit_publish must carry publisherName (3-80 chars, globally unique; 409 conflict = taken). Afterwards the name is fixed per account (rename with publisher_set) and publisherName on a publish is ignored. Requires the readWorkers scope.",
|
|
1054
|
+
auth: "manager",
|
|
1055
|
+
method: "get",
|
|
1056
|
+
schema: {},
|
|
1057
|
+
path: `${KITS_API}/publisher`,
|
|
1058
|
+
annotations: READ_ONLY,
|
|
1059
|
+
};
|
|
1060
|
+
const setMyPublisher = {
|
|
1061
|
+
name: "publisher_set",
|
|
1062
|
+
title: "Update My Publisher",
|
|
1063
|
+
description: "Edit the publisher profile: name (renames every listing; the slug never moves), description (the bio), links ([{kind, value}]), isListed (whether the profile appears in the directory). Every field optional — omit to keep. 404 until the first publish creates the profile. 409 conflict when the new name is already another publisher's — pick a different name; retrying the same one will not help. Requires the publishKits scope." +
|
|
1064
|
+
NEW_SCOPE_NOTE,
|
|
1065
|
+
auth: "manager",
|
|
1066
|
+
method: "put",
|
|
1067
|
+
schema: {
|
|
1068
|
+
name: z.string().min(3).max(80).optional().describe("New publisher name (3-80, globally unique)."),
|
|
1069
|
+
description: z.string().max(2000).optional().describe("Bio shown on the publisher page."),
|
|
1070
|
+
links: z.array(z.object({
|
|
1071
|
+
kind: z.string().describe("Link kind, e.g. website, x, github, linkedin."),
|
|
1072
|
+
value: z.string().describe("The handle or URL for that kind."),
|
|
1073
|
+
})).optional().describe("Full replace of the link list."),
|
|
1074
|
+
isListed: z.boolean().optional().describe("false hides the profile page from the directory; kits stay installable."),
|
|
1075
|
+
},
|
|
1076
|
+
path: `${KITS_API}/publisher`,
|
|
1077
|
+
bodyBuilder: (params) => ({
|
|
1078
|
+
name: params.name,
|
|
1079
|
+
description: params.description,
|
|
1080
|
+
links: params.links,
|
|
1081
|
+
isListed: params.isListed,
|
|
1082
|
+
}),
|
|
1083
|
+
annotations: UPDATE,
|
|
1084
|
+
};
|
|
1085
|
+
const listMyKits = {
|
|
1086
|
+
name: "my_kits_list",
|
|
1087
|
+
title: "List My Kits",
|
|
1088
|
+
description: "This account's kits, ANY status: slug, name, jobSentence, status (published | unlisted | private | removedByAdmin), moderationStatus (clear | flagged | underReview | takenDown — the truth about a public listing; the semantic scan can flag it MINUTES after the publish response, so re-read this after a public publish and read kit_scan_get for the findings), isProtected, apps, categories, categorySlots, requiredInputs, memorySetup, downloadCount, installedWorkerCount / workersBehind (this account's workers installed from each kit) and the dates. Requires the readWorkers scope.",
|
|
1089
|
+
auth: "manager",
|
|
1090
|
+
method: "get",
|
|
1091
|
+
schema: {},
|
|
1092
|
+
path: `${KITS_API}/mine`,
|
|
1093
|
+
annotations: READ_ONLY,
|
|
1094
|
+
};
|
|
1095
|
+
const validateKit = {
|
|
1096
|
+
name: "kit_validate",
|
|
1097
|
+
title: "Validate Kit",
|
|
1098
|
+
description: "Dry-run a publish: the SAME body kit_publish takes, judged by every publish gate at once — creates nothing. Returns canPublish (the verdict), errors[] and warnings[] each {section, message} (sections: metadata, categories, permissions, mcp, labels, scanner, scanner-llm, memory, appDescriptions, publisher, limits — the publish itself stops at the FIRST problem, this reports them all), lintWarnings[] (advisory structure nudges), requiredInputs[] (install-form fields the text declares), appLabels[], memorySetup[] (normalized) and manifestPreview (the exact permission manifest that would publish). The document is judged as a PUBLIC publish, so a private publish is strictly more permissive than its dry run — the one exception is the public-kit cap, which follows the body visibility because private kits are unlimited. Only authored content can be dry-run: sourceWorkerId has nothing to validate (400). Pass kitRef when the body will REPLACE an existing listing (kit_replace), so the 100-public-kit cap and the republish rules are judged against that listing. Repeat until canPublish is true, then kit_publish the identical body." +
|
|
1099
|
+
AUTHORING_FLOW_NOTE,
|
|
1100
|
+
auth: "manager",
|
|
1101
|
+
method: "post",
|
|
1102
|
+
schema: {
|
|
1103
|
+
...LISTING_SCHEMA,
|
|
1104
|
+
name: LISTING_SCHEMA.name.optional(),
|
|
1105
|
+
jobSentence: LISTING_SCHEMA.jobSentence.optional(),
|
|
1106
|
+
categorySlugs: LISTING_SCHEMA.categorySlugs.optional(),
|
|
1107
|
+
content: CONTENT_SCHEMA,
|
|
1108
|
+
kitRef: KIT_REF.optional().describe("Validate as a REPLACEMENT of this owned listing (slug) — pass what you will pass to kit_replace. Omit for a new listing."),
|
|
1109
|
+
},
|
|
1110
|
+
path: (params) => params.kitRef ? `${KITS_API}/validate?kitRef=${encodeURIComponent(String(params.kitRef))}` : `${KITS_API}/validate`,
|
|
1111
|
+
bodyBuilder: withoutKitRef,
|
|
1112
|
+
annotations: READ_ONLY,
|
|
1113
|
+
};
|
|
1114
|
+
const publishKit = {
|
|
1115
|
+
name: "kit_publish",
|
|
1116
|
+
title: "Publish Kit",
|
|
1117
|
+
description: "Publish a NEW kit on this account — from authored content, or from any worker on the account (sourceWorkerId: its instruction, texts, permissions, schedules and triggers become the kit; rule VALUES never travel, only that rules exist). Exactly one of content / sourceWorkerId. NOT idempotent: every successful call creates another listing — never retry a success; on a timeout read my_kits_list before trying again. visibility 'private' (recommended first) makes a kit only this account can install: kit_install its slug and you have a worker built from scratch through the reviewed manifest pipeline. 'public' (default) lists it in the directory after the fail-closed supply-chain scan — 503 kit_scan_unavailable means retry later with the SAME body, or publish private. 400 names the FIRST failing rule (run kit_validate first to see them all). 403 = the source worker was installed from a protected kit, whose text belongs to that kit's publisher. 409 = publisherName taken (first publish only). Caps: 20 publishes/hour per account, 100 PUBLIC kits per account (private kits are unlimited; an unlisted or admin-removed listing still holds its slot). Returns the listing (slug, status, moderationStatus, requiredInputs, lintWarnings, …); a public listing can still be flagged minutes later by the semantic scan — re-read my_kits_list, and kit_scan_get has the findings." +
|
|
1118
|
+
AUTHORING_FLOW_NOTE,
|
|
1119
|
+
auth: "manager",
|
|
1120
|
+
method: "post",
|
|
1121
|
+
schema: {
|
|
1122
|
+
...LISTING_SCHEMA,
|
|
1123
|
+
content: CONTENT_SCHEMA.optional(),
|
|
1124
|
+
sourceWorkerId: z.string().uuid().optional().describe("Publish from this worker instead of content (any worker on the account — its workerId from workers_list). Never together with content."),
|
|
1125
|
+
},
|
|
1126
|
+
path: KITS_API,
|
|
1127
|
+
annotations: CREATE,
|
|
1128
|
+
};
|
|
1129
|
+
const updateKit = {
|
|
1130
|
+
name: "kit_update",
|
|
1131
|
+
title: "Update Kit",
|
|
1132
|
+
description: "Edit a listing in place — PARTIAL: omitted fields stay unchanged, and the slug never moves. Metadata (name, jobSentence, description, categorySlugs, appDescriptions, recommendedClients, recommendedModel, declaredModelFloor, modelScores, isProtected), the served text (instructionContent, whenToUse, startCommand, endCommand, endCommandDescription, skillResources — full replace), memory (memoryProfile, selfFactsEnabled, memorySetup — full replace), and the surface: permissions + categorySlots replace TOGETHER as one unit (providing either rebuilds the whole surface; the other defaults to empty), contactAllowAll separately. Labels are validated against the FINAL text and manifest. A text change on a non-private kit re-runs the supply-chain scan (503 = retry later, nothing changed). For a whole-kit replacement that also swaps schedules, triggers and usage windows use kit_replace. Requires the publishKits scope." +
|
|
1133
|
+
NEW_SCOPE_NOTE,
|
|
1134
|
+
auth: "manager",
|
|
1135
|
+
method: "patch",
|
|
1136
|
+
schema: {
|
|
1137
|
+
kitRef: kitRefParam,
|
|
1138
|
+
name: LISTING_SCHEMA.name.optional(),
|
|
1139
|
+
jobSentence: LISTING_SCHEMA.jobSentence.optional(),
|
|
1140
|
+
description: LISTING_SCHEMA.description,
|
|
1141
|
+
categorySlugs: LISTING_SCHEMA.categorySlugs.optional(),
|
|
1142
|
+
appDescriptions: LISTING_SCHEMA.appDescriptions,
|
|
1143
|
+
recommendedClients: LISTING_SCHEMA.recommendedClients,
|
|
1144
|
+
recommendedModel: LISTING_SCHEMA.recommendedModel,
|
|
1145
|
+
declaredModelFloor: LISTING_SCHEMA.declaredModelFloor,
|
|
1146
|
+
modelScores: LISTING_SCHEMA.modelScores,
|
|
1147
|
+
isProtected: LISTING_SCHEMA.isProtected,
|
|
1148
|
+
instructionContent: z.string().min(1).max(100000).optional().describe("Replace the served instruction (1-100000)."),
|
|
1149
|
+
whenToUse: z.string().max(500).optional().describe("Replace the when-to-use line (≤500; '' clears)."),
|
|
1150
|
+
startCommand: z.string().max(20000).optional().describe("Replace the /start command (≤20000; '' clears)."),
|
|
1151
|
+
endCommand: z.string().max(20000).optional().describe("Replace the /end command (≤20000; '' clears)."),
|
|
1152
|
+
endCommandDescription: z.string().max(500).optional().describe("Replace when the worker should call /end (≤500; '' clears)."),
|
|
1153
|
+
skillResources: z.array(z.record(z.unknown())).max(20).optional().describe("Full replace of the skill resources ([] clears). Shape: kit_authoring_guide section 'schema'. Counts as a text edit."),
|
|
1154
|
+
permissions: z.array(z.record(z.unknown())).optional().describe("Full replace of the pinned-app permission selections (content.apps shape). Replaces TOGETHER with categorySlots."),
|
|
1155
|
+
categorySlots: z.array(z.record(z.unknown())).optional().describe("Full replace of the capability slots (content.categorySlots shape). Replaces TOGETHER with permissions; [] clears."),
|
|
1156
|
+
contactAllowAll: z.boolean().optional().describe("The token-wide contact/board stance: true allow-all, false deny-by-default."),
|
|
1157
|
+
memoryProfile: z.enum(["stateless", "contextual"]).optional().describe("The declared worker class."),
|
|
1158
|
+
selfFactsEnabled: z.boolean().optional().describe("Whether the instruction expects the worker to save its own facts."),
|
|
1159
|
+
memorySetup: z.array(z.record(z.unknown())).max(10).optional().describe("Full replace of the memory-setup questions ([] asks nothing). Shape: kit_authoring_guide section 'schema'."),
|
|
1160
|
+
},
|
|
1161
|
+
path: (params) => kitRefPath(params),
|
|
1162
|
+
bodyBuilder: withoutKitRef,
|
|
1163
|
+
annotations: UPDATE,
|
|
1164
|
+
};
|
|
1165
|
+
const replaceKit = {
|
|
1166
|
+
name: "kit_replace",
|
|
1167
|
+
title: "Replace Kit",
|
|
1168
|
+
description: "Replace a listing WHOLESALE from authored content — the same body as kit_publish with content (required; sourceWorkerId is not allowed here). Unlike kit_update this also replaces schedules, triggers, usage windows and trigger modes. visibility chooses the resulting state and is how a private kit is PROMOTED to public (runs the scan, and is the one republish the 100-public-kit cap can refuse — a kit already on the public lane keeps its slot) or a public one demoted. Timeframes, contact policy and mcpServers the content leaves null carry over from the stored manifest — a republish never silently downgrades them. The slug never changes for an already-public kit; a born-private kit takes its clean slug on its first public release. 400 on a listing an admin removed or that is taken down. Validate first: kit_validate with the same body and this kitRef. Requires the publishKits scope." +
|
|
1169
|
+
NEW_SCOPE_NOTE,
|
|
1170
|
+
auth: "manager",
|
|
1171
|
+
method: "put",
|
|
1172
|
+
schema: {
|
|
1173
|
+
kitRef: kitRefParam,
|
|
1174
|
+
...LISTING_SCHEMA,
|
|
1175
|
+
content: CONTENT_SCHEMA,
|
|
1176
|
+
},
|
|
1177
|
+
path: (params) => kitRefPath(params),
|
|
1178
|
+
bodyBuilder: withoutKitRef,
|
|
1179
|
+
annotations: UPDATE,
|
|
1180
|
+
};
|
|
1181
|
+
const getKitScan = {
|
|
1182
|
+
name: "kit_scan_get",
|
|
1183
|
+
title: "Get Kit Scan Report",
|
|
1184
|
+
description: "The listing's security-scan report, author-only: moderationStatus (clear | flagged | underReview | takenDown), moderationReason, moderationDecidedAtUtc, scannedAtUtc (null = never scanned), checks[] (the check families that ran), findings[] (what to fix, as SEVERITY:analyzer — Title), flagCodes[] (the machine codes behind a flag) and semanticScanPending (true = the deep pass has not landed yet — poll this after a public publish rather than assuming clear). The public listing never carries finding text. Requires the readWorkers scope.",
|
|
1185
|
+
auth: "manager",
|
|
1186
|
+
method: "get",
|
|
1187
|
+
schema: { kitRef: kitRefParam },
|
|
1188
|
+
path: (params) => kitRefPath(params, "/scan"),
|
|
1189
|
+
paramFilter: () => ({}),
|
|
1190
|
+
annotations: READ_ONLY,
|
|
1191
|
+
};
|
|
1192
|
+
const unpublishKit = {
|
|
1193
|
+
name: "kit_unpublish",
|
|
1194
|
+
title: "Unpublish Kit",
|
|
1195
|
+
description: "Withdraw a listing from the directory (status unlisted). Reversible with kit_relist — the non-destructive alternative to kit_delete. Workers already installed from it keep working: a kit is copied at install, never linked. Requires the publishKits scope." +
|
|
1196
|
+
NEW_SCOPE_NOTE,
|
|
1197
|
+
auth: "manager",
|
|
1198
|
+
method: "post",
|
|
1199
|
+
schema: { kitRef: kitRefParam },
|
|
1200
|
+
path: (params) => kitRefPath(params, "/unpublish"),
|
|
1201
|
+
bodyBuilder: () => ({}),
|
|
1202
|
+
annotations: UPDATE,
|
|
1203
|
+
};
|
|
1204
|
+
const relistKit = {
|
|
1205
|
+
name: "kit_relist",
|
|
1206
|
+
title: "Relist Kit",
|
|
1207
|
+
description: "Restore an UNLISTED kit to the public directory. Re-runs the fail-closed supply-chain scan (503 kit_scan_unavailable = retry later; nothing changed). Only for an unlisted kit — a PRIVATE kit goes public through kit_replace with visibility 'public'. Requires the publishKits scope." +
|
|
1208
|
+
NEW_SCOPE_NOTE,
|
|
1209
|
+
auth: "manager",
|
|
1210
|
+
method: "post",
|
|
1211
|
+
schema: { kitRef: kitRefParam },
|
|
1212
|
+
path: (params) => kitRefPath(params, "/relist"),
|
|
1213
|
+
bodyBuilder: () => ({}),
|
|
1214
|
+
annotations: UPDATE,
|
|
1215
|
+
};
|
|
1216
|
+
const makeKitPrivate = {
|
|
1217
|
+
name: "kit_make_private",
|
|
1218
|
+
title: "Make Kit Private",
|
|
1219
|
+
description: "Move a listing into this account's private library: still installable by this account (kit_install), hidden from the directory. Works from any status except removedByAdmin. Making it public again is kit_replace with visibility 'public', which re-runs the scan. Requires the publishKits scope." +
|
|
1220
|
+
NEW_SCOPE_NOTE,
|
|
1221
|
+
auth: "manager",
|
|
1222
|
+
method: "post",
|
|
1223
|
+
schema: { kitRef: kitRefParam },
|
|
1224
|
+
path: (params) => kitRefPath(params, "/make-private"),
|
|
1225
|
+
bodyBuilder: () => ({}),
|
|
1226
|
+
annotations: UPDATE,
|
|
1227
|
+
};
|
|
1228
|
+
const deleteKit = {
|
|
1229
|
+
name: "kit_delete",
|
|
1230
|
+
title: "Delete Kit",
|
|
1231
|
+
description: "Hard-delete a listing (204). PERMANENT — destroys its download history and stats; kit_unpublish is the reversible alternative. Workers installed from it are untouched. The second call is a 404. Requires the publishKits scope." +
|
|
1232
|
+
NEW_SCOPE_NOTE,
|
|
1233
|
+
auth: "manager",
|
|
1234
|
+
method: "delete",
|
|
1235
|
+
schema: { kitRef: kitRefParam },
|
|
1236
|
+
path: (params) => kitRefPath(params),
|
|
1237
|
+
successMessage: "Kit deleted.",
|
|
1238
|
+
annotations: DELETE,
|
|
1239
|
+
};
|
|
1240
|
+
// ─── Connected apps + model keys (manageConnections) ────────────────────────
|
|
1241
|
+
// The operational half of authoring: a worker can only use apps that are
|
|
1242
|
+
// connected on its operator. The read is in the kit vocabulary (the same app
|
|
1243
|
+
// codes content.apps[].code takes) so a kit's apps can be checked against it
|
|
1244
|
+
// verbatim; the write takes a credential, validated live and never returned.
|
|
1245
|
+
const APPS_API = "/api/manage/apps";
|
|
1246
|
+
const MODEL_KEYS_API = "/api/manage/model-keys";
|
|
1247
|
+
const OPERATOR_ID_HINT = "The operator (workspace) to act for, as a GUID from apps_list → operators[]. Omit for the account's default operator — the one installs and clones land on. Account-level providers ignore it.";
|
|
1248
|
+
const PROVIDER_HINT = "A recipe's provider code from apps_list → apps[].connect[].provider: telegram, discord, slack, twilio, sendgrid, resend, tavily, brave, exa, brightData, firecrawl, granola, krisp, gong, zendesk, shopify, hubspot, notion, linear, monday, jira, asana, confluence, or mcp:<slug> for an MCP app (a platform one, or one of your own from mcp_servers_list — published or not). Case-insensitive.";
|
|
1249
|
+
const listApps = {
|
|
1250
|
+
name: "apps_list",
|
|
1251
|
+
title: "List Connected Apps",
|
|
1252
|
+
description: "Which apps this operator can use RIGHT NOW, how each unconnected one gets connected, and every connection behind the grid — read it before installing or publishing a kit, and whenever a worker's readiness says app_not_connected. The customMcp entry lists the PUBLISHED MCP apps (platform ones and this account's own); an app the platform does not offer at all is added with mcp_server_create. apps[] has one entry per kit app code (the same codes a kit's content.apps[].code takes): connected (an ACTIVE connection exists, or the platform serves the app under its own key), providers (the provider codes behind it), and connect[] — one recipe per provider with auth (credential = POST its fields with app_connect | oauth = a human signs in at url | platform = nothing to connect | admin = an account admin connected it for everyone), scope (operator | account — account-level keys serve every operator), fields[] ({name, required, secret, hint} — what app_connect's credential takes), url (the dashboard page for OAuth consents: Google, Microsoft, GitHub, Reddit and OAuth gateways cannot be connected from a key), help (where the secret comes from), and for MCP apps connected (per gateway). connections[] lists every connection as ONE uniform row: provider + id (what app_disconnect takes as connectionId), apps served (a Google sign-in serves email, calendar and drive at once), name, operatorId (null = account-level), status (ACTIVE | REQUIRES_REAUTH | EXPIRED | …), requiresReauth (the credential exists but the provider stopped honouring it — reconnect), deletable (false = removed on the dashboard, not here). operators[] lists the account's active operators (isDefault = the one used when none is named); operatorId re-reads for another. warnings names families whose read failed (rows MISSING, not absent — retry before concluding). No secret is ever returned. Requires the readWorkers scope.",
|
|
1253
|
+
auth: "manager",
|
|
1254
|
+
method: "get",
|
|
1255
|
+
schema: {
|
|
1256
|
+
operatorId: z.string().uuid().optional().describe(OPERATOR_ID_HINT),
|
|
1257
|
+
},
|
|
1258
|
+
path: APPS_API,
|
|
1259
|
+
annotations: READ_ONLY,
|
|
1260
|
+
};
|
|
1261
|
+
const connectApp = {
|
|
1262
|
+
name: "app_connect",
|
|
1263
|
+
title: "Connect App",
|
|
1264
|
+
description: "Connect an app for this operator by credential — the same validated, encrypted paste-to-connect the dashboard's tiles do: the credential is checked LIVE against the provider before anything is stored, stored encrypted, and never returned by any read. Take provider and the credential's field names from apps_list → apps[].connect[] (auth 'credential' only); connecting a provider again replaces its stored credential. Returns 201 {app, provider, connection (the same row apps_list shows — its id is what app_disconnect takes), warnings (non-fatal findings from the live check, typically permission scopes the credential lacks)}. On your own MCP server with credentialScope account this sets the one shared credential. An app the platform does not offer at all is added first with mcp_server_create (which can carry the credential itself). Errors: 400 credential_invalid = the provider rejected it — the message says what to check, do not retry the same value; 502 provider_unavailable = could not verify, retry the SAME request later; 400 dashboard_only = an OAuth provider — hand a human the url in the message; 400 nothing_to_connect = a platform-served app; 400 managed_by_admin = an org-wide PLATFORM MCP app; 404 = an unknown mcp:<slug>; 422 unsupported_provider = not available in this environment. Rate limit: 30 connects/hour per account. Requires the manageConnections scope." +
|
|
1265
|
+
NEW_SCOPE_NOTE,
|
|
1266
|
+
auth: "manager",
|
|
1267
|
+
method: "post",
|
|
1268
|
+
schema: {
|
|
1269
|
+
provider: z.string().min(1).max(120).describe(PROVIDER_HINT),
|
|
1270
|
+
credential: z.record(z.unknown()).describe("The recipe's fields by name, e.g. {\"botToken\": \"…\"} for telegram, {\"apiKey\": \"…\"} for tavily, {\"email\": …, \"apiToken\": …, \"instanceUrl\": …} for jira, {\"token\": \"…\"} for a Bearer MCP app. An MCP app with auth None takes {}."),
|
|
1271
|
+
operatorId: z.string().uuid().optional().describe(OPERATOR_ID_HINT),
|
|
1272
|
+
label: z.string().max(200).optional().describe("Optional operator-facing label, where the family keeps one (messaging, web search, keys)."),
|
|
1273
|
+
},
|
|
1274
|
+
path: (params) => `${APPS_API}/${encodeURIComponent(String(params.provider))}/connect`,
|
|
1275
|
+
bodyBuilder: (params) => ({ operatorId: params.operatorId, label: params.label, credential: params.credential }),
|
|
1276
|
+
annotations: CREATE,
|
|
1277
|
+
};
|
|
1278
|
+
const disconnectApp = {
|
|
1279
|
+
name: "app_disconnect",
|
|
1280
|
+
title: "Disconnect App",
|
|
1281
|
+
description: "Remove one connection (204). Workers using the app lose it immediately and their readiness reports app_not_connected — say so before doing it. provider and connectionId are a row's provider and id from apps_list → connections[]; only rows with deletable:true can be removed here (400 dashboard_only for an OAuth consent, 400 managed_by_admin for an admin-managed gateway — both are removed on the dashboard). 404 = no such connection on this operator or account. Requires the manageConnections scope." +
|
|
1282
|
+
NEW_SCOPE_NOTE,
|
|
1283
|
+
auth: "manager",
|
|
1284
|
+
method: "delete",
|
|
1285
|
+
schema: {
|
|
1286
|
+
provider: z.string().min(1).max(120).describe(PROVIDER_HINT),
|
|
1287
|
+
connectionId: z.string().min(1).max(200).describe("The row's id from apps_list → connections[] (a bot id, workspace id, row id or provider code — opaque; copy it verbatim)."),
|
|
1288
|
+
operatorId: z.string().uuid().optional().describe(OPERATOR_ID_HINT),
|
|
1289
|
+
},
|
|
1290
|
+
// DELETE sends no body, so the optional operator rides the query string (the same promotion
|
|
1291
|
+
// kit_validate uses for kitRef).
|
|
1292
|
+
path: (params) => `${APPS_API}/${encodeURIComponent(String(params.provider))}/connections/${encodeURIComponent(String(params.connectionId))}` +
|
|
1293
|
+
(typeof params.operatorId === "string" && params.operatorId ? `?operatorId=${encodeURIComponent(params.operatorId)}` : ""),
|
|
1294
|
+
successMessage: "Connection removed.",
|
|
1295
|
+
annotations: DELETE,
|
|
1296
|
+
};
|
|
1297
|
+
const MODEL_PROVIDER_HINT = "The model provider: Anthropic | OpenAI | Google | XAI (case-insensitive).";
|
|
1298
|
+
const listModelKeys = {
|
|
1299
|
+
name: "model_keys_list",
|
|
1300
|
+
title: "List Model Keys",
|
|
1301
|
+
description: "The account's own model-provider API keys (bring-your-own-key): keys[] with provider, label, keySuffix + fingerprint (recognition only — the key itself is never returned), status ACTIVE | INVALID | REVOKED, lastErrorCode (why the provider rejected an INVALID key), and the timestamps. A key applies to every run on its provider automatically; an INVALID one means runs on that provider are being SKIPPED (skipReason ByokKeyInvalid) until it is replaced with model_key_set or removed with model_key_delete. includeRevoked lists removed keys too. 422 RUNTIME_NOT_ENABLED where hosted runs are off. Requires the readWorkers scope.",
|
|
1302
|
+
auth: "manager",
|
|
1303
|
+
method: "get",
|
|
1304
|
+
schema: {
|
|
1305
|
+
includeRevoked: z.boolean().default(false).describe("true = include keys that were removed."),
|
|
1306
|
+
},
|
|
1307
|
+
path: MODEL_KEYS_API,
|
|
1308
|
+
annotations: READ_ONLY,
|
|
1309
|
+
};
|
|
1310
|
+
const setModelKey = {
|
|
1311
|
+
name: "model_key_set",
|
|
1312
|
+
title: "Set Model Key",
|
|
1313
|
+
description: "Set or rotate the account's API key for a model provider. The key is probed live against the provider BEFORE it is stored — an unauthenticated key is never stored — then stored encrypted and never returned; re-pasting the current key is a quiet no-op. From the next run, runs on that provider bill the account's own key (1M free tokens a month, then a small fee on list price from the wallet — no platform fallback: if the provider later rejects the key, runs on it are skipped until it is replaced or removed). Errors: 400 provider_auth_failed = the provider rejected it, do not retry the same value; 400 key_malformed = a paste artifact (control or non-ASCII characters), re-copy it; 502 provider_unavailable = could not verify, retry the SAME request later; 503 vault_unconfigured = key storage is not available on this environment; 422 RUNTIME_NOT_ENABLED. Rate limit: 20 sets/hour per account. Requires the manageConnections scope." +
|
|
1314
|
+
NEW_SCOPE_NOTE,
|
|
1315
|
+
auth: "manager",
|
|
1316
|
+
method: "put",
|
|
1317
|
+
schema: {
|
|
1318
|
+
provider: z.string().min(1).max(40).describe(MODEL_PROVIDER_HINT),
|
|
1319
|
+
apiKey: z.string().min(8).max(4096).describe("The provider API key, verbatim."),
|
|
1320
|
+
label: z.string().max(200).optional().describe("Optional label shown beside the key."),
|
|
1321
|
+
},
|
|
1322
|
+
path: (params) => `${MODEL_KEYS_API}/${encodeURIComponent(String(params.provider))}`,
|
|
1323
|
+
bodyBuilder: (params) => ({ apiKey: params.apiKey, label: params.label }),
|
|
1324
|
+
annotations: UPDATE,
|
|
1325
|
+
};
|
|
1326
|
+
const deleteModelKey = {
|
|
1327
|
+
name: "model_key_delete",
|
|
1328
|
+
title: "Delete Model Key",
|
|
1329
|
+
description: "Remove the account's key for a model provider (204). Runs on its models return to platform billing from the next run; this also clears an INVALID key, which is how skipped runs on that provider resume. 404 not_found when no key is stored for the provider; 422 RUNTIME_NOT_ENABLED. Requires the manageConnections scope." +
|
|
1330
|
+
NEW_SCOPE_NOTE,
|
|
1331
|
+
auth: "manager",
|
|
1332
|
+
method: "delete",
|
|
1333
|
+
schema: {
|
|
1334
|
+
provider: z.string().min(1).max(40).describe(MODEL_PROVIDER_HINT),
|
|
1335
|
+
},
|
|
1336
|
+
path: (params) => `${MODEL_KEYS_API}/${encodeURIComponent(String(params.provider))}`,
|
|
1337
|
+
successMessage: "Model key removed.",
|
|
1338
|
+
annotations: DELETE,
|
|
1339
|
+
};
|
|
1340
|
+
// ─── Custom MCP servers — the account's own MCP apps ────────────────────────
|
|
1341
|
+
//
|
|
1342
|
+
// How an app the platform does not offer reaches a worker: register its MCP
|
|
1343
|
+
// server (the credential rides the same call), discover its tools, enable the
|
|
1344
|
+
// ones a job needs — which publishes it — and bind it in a kit's
|
|
1345
|
+
// content.mcpServers by id. The same steps the Kit Creator Studio runs; here a
|
|
1346
|
+
// machine runs them with the secret in hand.
|
|
1347
|
+
const MCP_SERVERS_API = "/api/manage/mcp-servers";
|
|
1348
|
+
const GATEWAY_ID_HINT = "The server's gatewayId handle (a GUID) from mcp_servers_list or the register's response — NOT the numeric id a kit binds.";
|
|
1349
|
+
const MCP_AUTH_TYPES = ["None", "Bearer", "ApiKeyHeader", "ApiKeyQuery", "Basic", "CustomHeaders", "McpOAuth"];
|
|
1350
|
+
const listMcpServers = {
|
|
1351
|
+
name: "mcp_servers_list",
|
|
1352
|
+
title: "List MCP Servers",
|
|
1353
|
+
description: "Every MCP server this account registered as its own custom MCP app — published or not — with tools, credential state and connect recipe; no secret is ever returned. servers[] by name: id (the value a kit binds as content.mcpServers[].gatewayId), gatewayId (the handle the other mcp_server_* tools take), slug (a published one appears in apps_list as mcp:<slug>), upstreamUrl (public listing data if a kit binding it publishes publicly), authType, credentialScope (operator = each operator connects its own credential | account = one credential for everyone), published (true once at least one tool is enabled — only then can workers reach it and kits bind it), status (the server's health), connected / sharedConnected / connectedOperatorIds, lastDiscoveredAt, lastDiscoveryError, connect (the recipe app_connect takes — provider, fields), tools[] (every discovered tool: toolId — what mcp_server_set_tools and content.mcpServers[].toolIds take — name, description, enabled, state). max is the per-account ceiling. Platform MCP apps are not here: they need no registration and are on kit_vocabulary (mcpApps). Requires the readWorkers scope.",
|
|
1354
|
+
auth: "manager",
|
|
1355
|
+
method: "get",
|
|
1356
|
+
schema: {},
|
|
1357
|
+
path: MCP_SERVERS_API,
|
|
1358
|
+
annotations: READ_ONLY,
|
|
1359
|
+
};
|
|
1360
|
+
const getMcpServer = {
|
|
1361
|
+
name: "mcp_server_get",
|
|
1362
|
+
title: "Get MCP Server",
|
|
1363
|
+
description: "One of this account's MCP servers, with every discovered tool (the same shape as a mcp_servers_list entry). 404 not_found = no such server on this account. Requires the readWorkers scope.",
|
|
1364
|
+
auth: "manager",
|
|
1365
|
+
method: "get",
|
|
1366
|
+
schema: {
|
|
1367
|
+
gatewayId: z.string().uuid().describe(GATEWAY_ID_HINT),
|
|
1368
|
+
},
|
|
1369
|
+
path: (params) => `${MCP_SERVERS_API}/${encodeURIComponent(String(params.gatewayId))}`,
|
|
1370
|
+
paramFilter: () => ({}),
|
|
1371
|
+
annotations: READ_ONLY,
|
|
1372
|
+
};
|
|
1373
|
+
const createMcpServer = {
|
|
1374
|
+
name: "mcp_server_create",
|
|
1375
|
+
title: "Register MCP Server",
|
|
1376
|
+
description: "Register an MCP server as this account's own custom MCP app — the way to reach an app the platform does not offer — and list its tools in the same call. upstreamUrl must be the MCP endpoint itself (https), never a docs page, and must come from the user or the vendor's docs — never guessed. authType is what the server expects: None (default — discovered in this call, no credential), Bearer {token}, ApiKeyHeader {header, value}, ApiKeyQuery {param, value}, Basic {username, password}, CustomHeaders {headers} or McpOAuth (a human completes the sign-in on the dashboard). credential carries those fields: probed LIVE against the server before anything is stored, stored encrypted, never returned, and the tools are discovered with it. credentialScope: operator (default — each operator connects its own; this call stores it for operatorId or the default operator) or account (one credential serves every operator). Omit credential to register now and connect later with app_connect (provider mcp:<slug>). Returns 201 {server (the mcp_servers_list shape — id is what a kit binds, gatewayId what the next tools take), discovery {added, changed, removed, unchanged}, next (what to do now)}. The server is UNPUBLISHED until mcp_server_set_tools enables at least one tool. Errors: 400 credential_invalid = the server rejected the credential (nothing was kept — fix it and retry); 502 provider_unavailable = the URL did not answer as an MCP server (nothing was kept — check the endpoint or retry later); 422 invalid_url = not an https URL on a public host; 400 invalid_request names the valid authType / credentialScope values or the 25-server ceiling. Tell the user: the upstream URL becomes public listing data if a kit binding this server publishes publicly. Rate limit: 20 registers/hour per account. Requires the manageConnections scope." +
|
|
1377
|
+
NEW_SCOPE_NOTE,
|
|
1378
|
+
auth: "manager",
|
|
1379
|
+
method: "post",
|
|
1380
|
+
schema: {
|
|
1381
|
+
name: z.string().min(1).max(200).describe("Display name, e.g. the product name."),
|
|
1382
|
+
upstreamUrl: z.string().min(8).max(2048).describe("The https MCP endpoint, verbatim from the user or the vendor's docs."),
|
|
1383
|
+
authType: z.enum(MCP_AUTH_TYPES).default("None").describe("What the server expects. Default None."),
|
|
1384
|
+
credentialScope: z.enum(["operator", "account"]).default("operator").describe("operator = each operator connects its own credential (default) | account = one credential for every operator."),
|
|
1385
|
+
credential: z.record(z.unknown()).optional().describe("The auth type's fields by name, e.g. {\"token\": \"…\"} for Bearer, {\"header\": \"X-Api-Key\", \"value\": \"…\"} for ApiKeyHeader. Omit for None, McpOAuth, or to connect later with app_connect."),
|
|
1386
|
+
operatorId: z.string().uuid().optional().describe("credentialScope operator: the operator the credential is stored for (a GUID from apps_list → operators[]). Omit for the account's default operator."),
|
|
1387
|
+
description: z.string().max(1000).optional().describe("Catalog blurb shown where the server is picked."),
|
|
1388
|
+
connectionInstructions: z.string().max(4000).optional().describe("Where a person finds the credential, per the vendor's docs (travels with a kit that binds this server)."),
|
|
1389
|
+
credentialHelpText: z.string().max(500).optional().describe("A one-line hint for the credential field, e.g. 'Personal API key'."),
|
|
1390
|
+
setupGuideUrl: z.string().max(2048).optional().describe("The vendor's setup page."),
|
|
1391
|
+
slug: z.string().max(60).optional().describe("Override the slug derived from the name (uppercase, unique within the account)."),
|
|
1392
|
+
namespacePrefix: z.string().max(20).optional().describe("Override the tool-name prefix derived from the slug ([a-z0-9_], ≤20)."),
|
|
1393
|
+
transport: z.enum(["AutoDetect", "StreamableHttp", "Sse"]).optional().describe("Default AutoDetect."),
|
|
1394
|
+
oauthScopes: z.string().max(2048).optional().describe("McpOAuth only: space-delimited scopes to request."),
|
|
1395
|
+
callTimeoutSeconds: z.number().int().min(1).max(600).optional().describe("Per-call timeout; default 60."),
|
|
1396
|
+
},
|
|
1397
|
+
path: MCP_SERVERS_API,
|
|
1398
|
+
bodyBuilder: (params) => ({
|
|
1399
|
+
name: params.name,
|
|
1400
|
+
upstreamUrl: params.upstreamUrl,
|
|
1401
|
+
authType: params.authType,
|
|
1402
|
+
credentialScope: params.credentialScope,
|
|
1403
|
+
credential: params.credential,
|
|
1404
|
+
operatorId: params.operatorId,
|
|
1405
|
+
description: params.description,
|
|
1406
|
+
connectionInstructions: params.connectionInstructions,
|
|
1407
|
+
credentialHelpText: params.credentialHelpText,
|
|
1408
|
+
setupGuideUrl: params.setupGuideUrl,
|
|
1409
|
+
slug: params.slug,
|
|
1410
|
+
namespacePrefix: params.namespacePrefix,
|
|
1411
|
+
transport: params.transport,
|
|
1412
|
+
oAuthScopes: params.oauthScopes,
|
|
1413
|
+
callTimeoutSeconds: params.callTimeoutSeconds,
|
|
1414
|
+
}),
|
|
1415
|
+
annotations: CREATE,
|
|
1416
|
+
};
|
|
1417
|
+
const discoverMcpServer = {
|
|
1418
|
+
name: "mcp_server_discover",
|
|
1419
|
+
title: "Discover MCP Server Tools",
|
|
1420
|
+
description: "List the server's tools again with its STORED credential (the shared one for scope account, operatorId's — the default operator's when omitted — for scope operator; never a pasted one): new tools land withheld, changed ones drop out until re-enabled with mcp_server_set_tools, vanished ones are removed. Returns {server, discovery {added, changed, removed, unchanged}, next}. 422 not_connected = nobody has connected this server yet — the message names the fix (app_connect with provider mcp:<slug>, or the dashboard for McpOAuth); 502 provider_unavailable = the server did not list its tools — retry later, or reconnect if the credential was revoked. Rate limit: 60 discoveries/hour per account. Requires the manageConnections scope." +
|
|
1421
|
+
NEW_SCOPE_NOTE,
|
|
1422
|
+
auth: "manager",
|
|
1423
|
+
method: "post",
|
|
1424
|
+
schema: {
|
|
1425
|
+
gatewayId: z.string().uuid().describe(GATEWAY_ID_HINT),
|
|
1426
|
+
operatorId: z.string().uuid().optional().describe("Scope operator: whose stored credential to list with. Omit for the account's default operator."),
|
|
1427
|
+
},
|
|
1428
|
+
path: (params) => `${MCP_SERVERS_API}/${encodeURIComponent(String(params.gatewayId))}/discover`,
|
|
1429
|
+
bodyBuilder: (params) => ({ operatorId: params.operatorId }),
|
|
1430
|
+
annotations: UPDATE,
|
|
1431
|
+
};
|
|
1432
|
+
const setMcpServerTools = {
|
|
1433
|
+
name: "mcp_server_set_tools",
|
|
1434
|
+
title: "Set MCP Server Tools",
|
|
1435
|
+
description: "Set the COMPLETE set of enabled tools on one of this account's MCP servers. Enabling at least one PUBLISHES the server: it appears in apps_list as mcp:<slug>, workers on operators where it is connected can call the enabled tools, and a kit binds them with content.mcpServers[{gatewayId: server.id, toolIds}]. Enabling none unpublishes it. Enable only what the job needs — every other tool reverts to withheld (blocked ones are untouched). enabledToolIds are toolId values from tools[]; 400 invalid_request names a toolId that is not on this server. Returns {server, next}. Requires the manageConnections scope." +
|
|
1436
|
+
NEW_SCOPE_NOTE,
|
|
1437
|
+
auth: "manager",
|
|
1438
|
+
method: "put",
|
|
1439
|
+
schema: {
|
|
1440
|
+
gatewayId: z.string().uuid().describe(GATEWAY_ID_HINT),
|
|
1441
|
+
enabledToolIds: z.array(z.number().int().nonnegative()).max(500).describe("The complete set of toolId values to enable (from tools[]); [] unpublishes the server."),
|
|
1442
|
+
},
|
|
1443
|
+
path: (params) => `${MCP_SERVERS_API}/${encodeURIComponent(String(params.gatewayId))}/tools`,
|
|
1444
|
+
bodyBuilder: (params) => ({ enabledToolIds: params.enabledToolIds }),
|
|
1445
|
+
annotations: UPDATE,
|
|
1446
|
+
};
|
|
1447
|
+
const deleteMcpServer = {
|
|
1448
|
+
name: "mcp_server_delete",
|
|
1449
|
+
title: "Delete MCP Server",
|
|
1450
|
+
description: "Delete one of this account's MCP servers (204): its tools, every stored credential and every worker binding go with it, so workers granted its tools lose them immediately and a kit that binds it can no longer be republished from its draft — say so before doing it. 404 not_found = no such server on this account; 403 forbidden = the key's owner neither registered it nor is an account admin. Requires the manageConnections scope." +
|
|
1451
|
+
NEW_SCOPE_NOTE,
|
|
1452
|
+
auth: "manager",
|
|
1453
|
+
method: "delete",
|
|
1454
|
+
schema: {
|
|
1455
|
+
gatewayId: z.string().uuid().describe(GATEWAY_ID_HINT),
|
|
1456
|
+
},
|
|
1457
|
+
path: (params) => `${MCP_SERVERS_API}/${encodeURIComponent(String(params.gatewayId))}`,
|
|
1458
|
+
successMessage: "MCP server deleted.",
|
|
1459
|
+
annotations: DELETE,
|
|
1460
|
+
};
|
|
879
1461
|
// ─── Export ─────────────────────────────────────────────────────────────────
|
|
880
1462
|
export const manageDescriptors = [
|
|
881
1463
|
getKeyInfo,
|
|
@@ -921,5 +1503,36 @@ export const manageDescriptors = [
|
|
|
921
1503
|
setWorkerBudget,
|
|
922
1504
|
getFleetBudget,
|
|
923
1505
|
setFleetBudget,
|
|
1506
|
+
getWorkerPermissions,
|
|
1507
|
+
listModels,
|
|
1508
|
+
listDeployments,
|
|
1509
|
+
getDeployment,
|
|
1510
|
+
deployWorker,
|
|
1511
|
+
updateDeployment,
|
|
1512
|
+
undeployWorker,
|
|
1513
|
+
getMyPublisher,
|
|
1514
|
+
setMyPublisher,
|
|
1515
|
+
listMyKits,
|
|
1516
|
+
validateKit,
|
|
1517
|
+
publishKit,
|
|
1518
|
+
updateKit,
|
|
1519
|
+
replaceKit,
|
|
1520
|
+
getKitScan,
|
|
1521
|
+
unpublishKit,
|
|
1522
|
+
relistKit,
|
|
1523
|
+
makeKitPrivate,
|
|
1524
|
+
deleteKit,
|
|
1525
|
+
listApps,
|
|
1526
|
+
connectApp,
|
|
1527
|
+
disconnectApp,
|
|
1528
|
+
listModelKeys,
|
|
1529
|
+
setModelKey,
|
|
1530
|
+
deleteModelKey,
|
|
1531
|
+
listMcpServers,
|
|
1532
|
+
getMcpServer,
|
|
1533
|
+
createMcpServer,
|
|
1534
|
+
discoverMcpServer,
|
|
1535
|
+
setMcpServerTools,
|
|
1536
|
+
deleteMcpServer,
|
|
924
1537
|
];
|
|
925
1538
|
//# sourceMappingURL=manage.js.map
|