@workerkit/core 0.3.0 → 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 CHANGED
@@ -6,6 +6,51 @@ and the package adheres to [Semantic Versioning](https://semver.org/).
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.3.1] - 2026-09-11
10
+
11
+ ### Added
12
+
13
+ Six manager descriptors: the hosted-deployment lane. Deploying a worker existed
14
+ only in the WorkerKit dashboard, so a worker installed through the API, MCP or
15
+ the CLI was created fully configured and could never run — its schedules never
16
+ fired and `worker_run` answered 409 `not_deployed` with nothing in the toolset
17
+ able to fix it. The registry now ships 74 manager and 9 anonymous descriptors.
18
+
19
+ - **`worker_deploy`** puts a worker on the hosted runtime: pick the model and
20
+ the per-run / per-day spend ceilings, or send nothing and take the kit's
21
+ recommended model with the platform defaults. Its refusals are the deploy
22
+ gauntlet and each names its fix — no instruction, an app still unconnected,
23
+ an unfunded wallet, a model the account's tier cannot reach.
24
+ - **`deployment_get`** and **`deployments_list`** read one deployment or every
25
+ deployed worker on the account — the fleet answer to which workers can
26
+ actually run. A worker in `workers_list` but not here is inert.
27
+ - **`deployment_update`** changes a live deployment (model, reasoning,
28
+ transcript retention, ceilings) and pauses or resumes it. Pause is the
29
+ reversible stop that keeps the model and the ceilings.
30
+ - **`worker_undeploy`** takes a worker off the runtime, keeping the worker, its
31
+ instruction, memory, schedules and its own key.
32
+ - **`models_list`** is the model picker the deployment calls need: what this
33
+ account may deploy on, priced per million tokens, with the reasoning
34
+ vocabulary each model accepts and the providers the account holds its own key
35
+ for.
36
+
37
+ ### Changed
38
+
39
+ - **`kit_install`** takes `deploy` and `deployment`, so installing and deploying
40
+ are one call. A deploy refused after the install still returns 201 with
41
+ `deploymentError` naming what to fix — the worker exists either way, so the
42
+ fix is `worker_deploy`, never a second install.
43
+ - **`workers_list`** and **`worker_get`** now describe the `deployment` field
44
+ each worker carries, and that `null` there means the worker will never run
45
+ whatever its readiness says.
46
+
47
+ ### Note
48
+
49
+ The deployment writes need the new `manageDeployments` scope. Like every scope
50
+ younger than the manager-key surface, a key minted before it existed does not
51
+ carry it — including one minted with "all" — so a 403 there is fixed by an
52
+ account admin re-scoping the key, never by retrying.
53
+
9
54
  ## [0.3.0] - 2026-09-10
10
55
 
11
56
  ### Added
package/README.md CHANGED
@@ -6,7 +6,7 @@
6
6
  [![license](https://img.shields.io/npm/l/%40workerkit%2Fcore.svg)](LICENSE)
7
7
 
8
8
  The shareable [WorkerKit](https://workerkit.ai) surface: an HTTP API client, the
9
- declarative tool-descriptor registry (68 authenticated fleet-management tools +
9
+ declarative tool-descriptor registry (74 authenticated fleet-management tools +
10
10
  9 anonymous kits-directory tools), and the single wire-execution path that turns
11
11
  a descriptor plus parameters into exactly one upstream request.
12
12
 
@@ -3,7 +3,7 @@ export type { HttpMethod, ToolAnnotations, ToolDescriptor } from "./types.js";
3
3
  export { manageDescriptors } from "./manage.js";
4
4
  export { directoryDescriptors, kitDetailData, kitDetailFooter, kitListData, listFooter, publisherPageData, publisherPageFooter, } from "./directory.js";
5
5
  import type { ToolDescriptor } from "./types.js";
6
- /** Every descriptor this package ships — 68 manager + 9 anonymous. */
6
+ /** Every descriptor this package ships — 74 manager + 9 anonymous. */
7
7
  export declare const allDescriptors: readonly ToolDescriptor[];
8
8
  /** Look a descriptor up by its tool name. */
9
9
  export declare function byName(name: string): ToolDescriptor | undefined;
@@ -3,7 +3,7 @@ export { manageDescriptors } from "./manage.js";
3
3
  export { directoryDescriptors, kitDetailData, kitDetailFooter, kitListData, listFooter, publisherPageData, publisherPageFooter, } from "./directory.js";
4
4
  import { manageDescriptors } from "./manage.js";
5
5
  import { directoryDescriptors } from "./directory.js";
6
- /** Every descriptor this package ships — 68 manager + 9 anonymous. */
6
+ /** Every descriptor this package ships — 74 manager + 9 anonymous. */
7
7
  export const allDescriptors = [
8
8
  ...manageDescriptors,
9
9
  ...directoryDescriptors,
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/descriptors/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,MAAM,EACN,MAAM,EACN,SAAS,EACT,OAAO,EACP,MAAM,GACP,MAAM,YAAY,CAAC;AAGpB,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EACL,oBAAoB,EACpB,aAAa,EACb,eAAe,EACf,WAAW,EACX,UAAU,EACV,iBAAiB,EACjB,mBAAmB,GACpB,MAAM,gBAAgB,CAAC;AAGxB,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAEtD,sEAAsE;AACtE,MAAM,CAAC,MAAM,cAAc,GAA8B;IACvD,GAAG,iBAAiB;IACpB,GAAG,oBAAoB;CACxB,CAAC;AAEF,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;AAEpE,6CAA6C;AAC7C,MAAM,UAAU,MAAM,CAAC,IAAY;IACjC,OAAO,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;AAC/B,CAAC","sourcesContent":["export {\n CREATE,\n DELETE,\n READ_ONLY,\n TRIGGER,\n UPDATE,\n} from \"./types.js\";\nexport type { HttpMethod, ToolAnnotations, ToolDescriptor } from \"./types.js\";\n\nexport { manageDescriptors } from \"./manage.js\";\nexport {\n directoryDescriptors,\n kitDetailData,\n kitDetailFooter,\n kitListData,\n listFooter,\n publisherPageData,\n publisherPageFooter,\n} from \"./directory.js\";\n\nimport type { ToolDescriptor } from \"./types.js\";\nimport { manageDescriptors } from \"./manage.js\";\nimport { directoryDescriptors } from \"./directory.js\";\n\n/** Every descriptor this package ships — 68 manager + 9 anonymous. */\nexport const allDescriptors: readonly ToolDescriptor[] = [\n ...manageDescriptors,\n ...directoryDescriptors,\n];\n\nconst byNameIndex = new Map(allDescriptors.map((d) => [d.name, d]));\n\n/** Look a descriptor up by its tool name. */\nexport function byName(name: string): ToolDescriptor | undefined {\n return byNameIndex.get(name);\n}\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/descriptors/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,MAAM,EACN,MAAM,EACN,SAAS,EACT,OAAO,EACP,MAAM,GACP,MAAM,YAAY,CAAC;AAGpB,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EACL,oBAAoB,EACpB,aAAa,EACb,eAAe,EACf,WAAW,EACX,UAAU,EACV,iBAAiB,EACjB,mBAAmB,GACpB,MAAM,gBAAgB,CAAC;AAGxB,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAEtD,sEAAsE;AACtE,MAAM,CAAC,MAAM,cAAc,GAA8B;IACvD,GAAG,iBAAiB;IACpB,GAAG,oBAAoB;CACxB,CAAC;AAEF,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;AAEpE,6CAA6C;AAC7C,MAAM,UAAU,MAAM,CAAC,IAAY;IACjC,OAAO,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;AAC/B,CAAC","sourcesContent":["export {\n CREATE,\n DELETE,\n READ_ONLY,\n TRIGGER,\n UPDATE,\n} from \"./types.js\";\nexport type { HttpMethod, ToolAnnotations, ToolDescriptor } from \"./types.js\";\n\nexport { manageDescriptors } from \"./manage.js\";\nexport {\n directoryDescriptors,\n kitDetailData,\n kitDetailFooter,\n kitListData,\n listFooter,\n publisherPageData,\n publisherPageFooter,\n} from \"./directory.js\";\n\nimport type { ToolDescriptor } from \"./types.js\";\nimport { manageDescriptors } from \"./manage.js\";\nimport { directoryDescriptors } from \"./directory.js\";\n\n/** Every descriptor this package ships — 74 manager + 9 anonymous. */\nexport const allDescriptors: readonly ToolDescriptor[] = [\n ...manageDescriptors,\n ...directoryDescriptors,\n];\n\nconst byNameIndex = new Map(allDescriptors.map((d) => [d.name, d]));\n\n/** Look a descriptor up by its tool name. */\nexport function byName(name: string): ToolDescriptor | undefined {\n return byNameIndex.get(name);\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"manage.d.ts","sourceRoot":"","sources":["../../src/descriptors/manage.ts"],"names":[],"mappings":"AAMA,OAAO,EAA8C,KAAK,cAAc,EAAE,MAAM,YAAY,CAAC;AA2pD7F,eAAO,MAAM,iBAAiB,EAAE,SAAS,cAAc,EAqEtD,CAAC"}
1
+ {"version":3,"file":"manage.d.ts","sourceRoot":"","sources":["../../src/descriptors/manage.ts"],"names":[],"mappings":"AAMA,OAAO,EAA8C,KAAK,cAAc,EAAE,MAAM,YAAY,CAAC;AAo0D7F,eAAO,MAAM,iBAAiB,EAAE,SAAS,cAAc,EA2EtD,CAAC"}
@@ -1,4 +1,4 @@
1
- // The 62 authenticated fleet-management tools, as pure descriptors. 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), and live state (isRunning, inFlightRuns, currentRunId — poll run_get with currentRunId to watch it). 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).",
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), 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), 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,122 @@ 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
+ };
879
1005
  // ─── Permissions (read-only) ────────────────────────────────────────────────
880
1006
  const getWorkerPermissions = {
881
1007
  name: "worker_permissions_get",
@@ -969,7 +1095,7 @@ const listMyKits = {
969
1095
  const validateKit = {
970
1096
  name: "kit_validate",
971
1097
  title: "Validate Kit",
972
- 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 (visibility is a publish-time choice, not validated), so a private publish is strictly more permissive than its dry run. 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 50-kit cap and the republish rules are judged against that listing. Repeat until canPublish is true, then kit_publish the identical body." +
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." +
973
1099
  AUTHORING_FLOW_NOTE,
974
1100
  auth: "manager",
975
1101
  method: "post",
@@ -988,7 +1114,7 @@ const validateKit = {
988
1114
  const publishKit = {
989
1115
  name: "kit_publish",
990
1116
  title: "Publish Kit",
991
- 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, 50 published kits per account. 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." +
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." +
992
1118
  AUTHORING_FLOW_NOTE,
993
1119
  auth: "manager",
994
1120
  method: "post",
@@ -1039,7 +1165,7 @@ const updateKit = {
1039
1165
  const replaceKit = {
1040
1166
  name: "kit_replace",
1041
1167
  title: "Replace Kit",
1042
- 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) 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." +
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." +
1043
1169
  NEW_SCOPE_NOTE,
1044
1170
  auth: "manager",
1045
1171
  method: "put",
@@ -1378,6 +1504,12 @@ export const manageDescriptors = [
1378
1504
  getFleetBudget,
1379
1505
  setFleetBudget,
1380
1506
  getWorkerPermissions,
1507
+ listModels,
1508
+ listDeployments,
1509
+ getDeployment,
1510
+ deployWorker,
1511
+ updateDeployment,
1512
+ undeployWorker,
1381
1513
  getMyPublisher,
1382
1514
  setMyPublisher,
1383
1515
  listMyKits,