@workerkit/core 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- // The 42 authenticated fleet-management tools, as pure descriptors. The
1
+ // The 62 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.
@@ -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), 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: {
@@ -183,7 +183,7 @@ const getRunEvents = {
183
183
  const getRunQuestion = {
184
184
  name: "run_question",
185
185
  title: "Get Run Question",
186
- description: "The question a run stopped to ask, and everything needed to answer it. A run whose status is AwaitingInput neither failed nor finished — it ENDED BY ASKING, settling its usage and releasing its wallet reserve, because a run cannot hold a request slot and money open across a human's reply. Returns questionId, runId, question, options (the closed set the run offered, when it offered one — free text otherwise), askedAtUtc, expiresAtUtc (null = it waits indefinitely), chainDepth, maxChainDepth, and once answered also answer, answeredAtUtc and resumeRunId. READ chainDepth AGAINST maxChainDepth: it is the only thing that says whether another exchange is possible — a chain already AT the limit still records an answer but starts no run, so run_answer there comes back chain_limit. Take maxChainDepth from this response rather than assuming a number. resumeRunId names the run an answer started, and THE RUN ID CHANGES across that boundary: follow resumeRunId instead of expecting this run to come back to life. expiresAtUtc is a real deadline — once it passes the platform closes the question out itself, and depending on the worker's timeout policy that either ends the exchange or resumes it with no answer at all, so answer before it rather than after. A 404 not_found means this run is not waiting on a question (it never asked, or the runId is wrong) — a normal answer, not an error. Requires the readRuns scope." +
186
+ description: "The question a run stopped to ask, and everything needed to answer it. A run whose status is AwaitingInput neither failed nor finished — it ENDED BY ASKING, settling its usage and releasing its wallet reserve, because a run cannot hold a request slot and money open across a human's reply. Returns questionId, runId, question, options (the closed set the run offered, when it offered one — free text otherwise), askedAtUtc, expiresAtUtc (null = it waits indefinitely), chainDepth, maxChainDepth, and once answered also answer, answeredAtUtc and resumeRunId. READ chainDepth AGAINST maxChainDepth: it is the only thing that says whether another exchange is possible — a chain already AT the limit still records an answer but starts no run, so run_answer there comes back chain_limit. Take maxChainDepth from this response rather than assuming a number. resumeRunId names the run an answer started, and THE RUN ID CHANGES across that boundary: follow resumeRunId instead of expecting this run to come back to life. expiresAtUtc is a real deadline — once it passes the platform closes the question out itself (it never resumes a run with no answer); a late answer still starts the run, but answer before it rather than after. A 404 not_found means this run is not waiting on a question (it never asked, or the runId is wrong) — a normal answer, not an error. Requires the readRuns scope." +
187
187
  RUNTIME_NOTE,
188
188
  auth: "manager",
189
189
  method: "get",
@@ -197,7 +197,7 @@ const getRunQuestion = {
197
197
  const answerRunQuestion = {
198
198
  name: "run_answer",
199
199
  title: "Answer Run Question",
200
- description: "Answer the question a suspended run asked. THE SUSPENDED RUN DOES NOT RESUME: answering mints a LINKED FOLLOW-ON RUN carrying your answer and the parent's context, and what comes back is THAT run's receipt — a DIFFERENT runId. Plan for a CHAIN of receipts rather than one long-lived run: watch the new runId with run_get / run_events, and note that the only link you can READ is forward — run_question on the parent carries resumeRunId, while the follow-on's own receipt has no pointer back — so keep the chain yourself as you walk it. It rides runWorkers rather than readRuns because it STARTS A RUN and costs money. Answering twice is IDEMPOTENT — the second call returns the run the first answer started instead of minting another. The follow-on goes through THE SAME GAUNTLET as any other run, so a 200 can still carry status Skipped with a skipReason (wallet credits, the worker's daily run or spend cap, the account's fleet ceiling, concurrency, readiness): that is a normal receipt, not an error, and because a Skipped resume is deliberately NOT linked to the question you may answer again once the reason clears. A chain already at maxChainDepth (read it from run_question) RECORDS the answer and refuses with 400 chain_limit rather than looping — terminal: start a fresh run with what you have learned instead of retrying. Other refusals use the {error, message} envelope where error IS the snake_case code: 404 not_found (that run is not waiting on a question), 400 already_answered (it was answered and its run never started). Requires the runWorkers scope." +
200
+ description: "Answer the question a suspended run asked. THE SUSPENDED RUN DOES NOT RESUME: answering mints a LINKED FOLLOW-ON RUN carrying your answer and the parent's context, and what comes back is THAT run's receipt — a DIFFERENT runId. Plan for a CHAIN of receipts rather than one long-lived run: watch the new runId with run_get / run_events, and note that the only link you can READ is forward — run_question on the parent carries resumeRunId, while the follow-on's own receipt has no pointer back — so keep the chain yourself as you walk it. It rides runWorkers rather than readRuns because it STARTS A RUN and costs money. Answering twice is IDEMPOTENT — the second call returns the run the first answer started instead of minting another. The follow-on goes through THE SAME GAUNTLET as any other run, so a 200 can still carry status Skipped with a skipReason (wallet credits, the worker's daily run or spend cap, the account's fleet ceiling, concurrency, readiness): that is a normal receipt, not an error, and because a Skipped resume is deliberately NOT linked to the question you may answer again once the reason clears. A chain already at maxChainDepth (read it from run_question) RECORDS the answer and refuses with 400 chain_limit rather than looping, on every retry — terminal: start a fresh run with what you have learned instead of retrying. Two simultaneous answers race: the second gets 409 answer_in_progress and should re-read run_question, whose resumeRunId will name the run the first one started. A late answer to a question that already timed out still starts the run. The one other refusal uses the {error, message} envelope where error IS the snake_case code: 404 not_found (that run is not waiting on a question). Requires the runWorkers scope." +
201
201
  RUNTIME_NOTE,
202
202
  auth: "manager",
203
203
  method: "post",
@@ -412,8 +412,8 @@ const deleteSchedule = {
412
412
  // Where the PLATFORM sends a worker's report when a run finishes. The two facts an
413
413
  // agent gets wrong by default: availability is judged at ACCOUNT level (never from
414
414
  // the worker's own apps), and a receipt is not a guarantee.
415
- const DELIVERY_NOTE = " How delivery works: the send is PLATFORM-level and detached from the worker's own permissions — the destination row you configure IS the permission, so a worker with no email app still delivers to email. The ONE hard configuration refusal is 400 CHANNEL_NOT_CONNECTED, judged at ACCOUNT level (the operator's connected apps), so NEVER infer availability from worker_get's apps — call delivery_channels. A worker holds at most 5 destinations. Skipped and Canceled runs never deliver: there is no result to send. Sends are AT-MOST-ONCE — never double-sent, but a crash at the wrong moment can lose one, so the run receipt's deliveries[] is a RECEIPT of what happened, not a guarantee that it did.";
416
- const DELIVERY_TARGET_HINT = "The destination. ONE shape serves every channel — set only the fields YOUR channel needs; anything else is dropped. email: to[1–10 addresses] (+ optional connectionId OR sendFrom to choose the mailbox on a multi-mailbox account). slack: channelId (+ optional workspaceId). msTeams: EITHER teamId+channelId (post to a channel) OR chatId (post to a chat) — exactly one form; both or neither is a 400. telegram: chatId, a numeric id or an @username (+ optional botId). notion: boardId — the board/database that gets one item per run with the report as its content. discord: channelId (+ optional applicationId). messaging: provider (e.g. 'TWILIO'), messagingChannel ('sms' | 'whatsapp'), to[EXACTLY ONE phone number] (+ optional from; omit for the connection's default sender).";
415
+ const DELIVERY_NOTE = " How delivery works: the send is PLATFORM-level and detached from the worker's own permissions — the destination row you configure IS the permission, so a worker with no email app still delivers to email. The ONE hard configuration refusal is 400 CHANNEL_NOT_CONNECTED, judged at ACCOUNT level (the operator's connected apps), so NEVER infer availability from worker_get's apps — call delivery_channels. A worker holds at most 5 destinations. Canceled runs never deliver on any channel. Skipped runs (refused at the gate) deliver to WEBHOOK destinations only, as a run.blocked event, once per worker per skip reason per UTC day. A run that ended by ASKING its owner a question (status AwaitingInput) delivers everywhere: the question is the message. Sends are AT-MOST-ONCE — never double-sent, but a crash at the wrong moment can lose one, so the run receipt's deliveries[] is a RECEIPT of what happened, not a guarantee that it did.";
416
+ const DELIVERY_TARGET_HINT = "The destination. ONE shape serves every channel — set only the fields YOUR channel needs; anything else is dropped. email: to[1–10 addresses] (+ optional connectionId OR sendFrom to choose the mailbox on a multi-mailbox account). slack: channelId (+ optional workspaceId). msTeams: EITHER teamId+channelId (post to a channel) OR chatId (post to a chat) — exactly one form; both or neither is a 400. telegram: chatId, a numeric id or an @username (+ optional botId). notion: boardId — the board/database that gets one item per run with the report as its content. discord: channelId (+ optional applicationId). messaging: provider (e.g. 'TWILIO'), messagingChannel ('sms' | 'whatsapp'), to[EXACTLY ONE phone number] (+ optional from; omit for the connection's default sender). webhook: url — an absolute https URL that resolves to a PUBLIC address (private, loopback and metadata ranges are a 400 at configuration and refused again at send time; redirects are never followed).";
417
417
  const DELIVERY_TARGET_SHAPE = {
418
418
  to: z.array(z.string()).optional().describe("email: 1–10 recipient addresses. messaging: exactly one phone number. Unused by every other channel."),
419
419
  connectionId: z.number().int().optional().describe("email only: send from this provider connection (multi-mailbox operators). Omit for the default mailbox."),
@@ -428,8 +428,9 @@ const DELIVERY_TARGET_SHAPE = {
428
428
  provider: z.string().optional().describe("messaging only: the provider code, e.g. 'TWILIO'."),
429
429
  messagingChannel: z.enum(["sms", "whatsapp"]).optional().describe("messaging only: which messaging channel to send over."),
430
430
  from: z.string().optional().describe("messaging only: sender number/id. Omit for the connection's default sender."),
431
+ url: z.string().url().optional().describe("webhook only: the absolute https URL that receives the signed run event. Must resolve to a public address; redirects are not followed."),
431
432
  };
432
- const DELIVERY_CONDITION_HINT = "Which terminal outcomes fire this destination: always, successOnly (Succeeded), or failureOnly (Failed / TimedOut / BudgetExceeded — the alerting shape). Skipped and Canceled runs never deliver on any condition.";
433
+ const DELIVERY_CONDITION_HINT = "Which terminal outcomes fire this destination: always, successOnly (Succeeded), or failureOnly — everything that needs attention: Failed / TimedOut / BudgetExceeded, a run waiting on an answer (AwaitingInput), and on a webhook a refused run (Skipped). Canceled never delivers on any condition; Skipped never reaches a chat or email destination.";
433
434
  const DELIVERY_CONTENT_MODE_HINT = "What the message carries: full = the run's finalDigest, its OWN closing report (falls back to the distilled summary); summary = the distilled summary of what the run actually did (falls back to finalDigest) — the right choice for short-form channels like SMS.";
434
435
  const listDeliveries = {
435
436
  name: "delivery_list",
@@ -448,7 +449,7 @@ const listDeliveries = {
448
449
  const deliveryChannels = {
449
450
  name: "delivery_channels",
450
451
  title: "List Delivery Channels",
451
- description: "Which delivery channels this worker's operator can send through RIGHT NOW: {email, slack, msTeams, telegram, notion, discord, messaging}, each true or false. Call this BEFORE delivery_create and pick a channel that is true — creating on a false one is a guaranteed 400 CHANNEL_NOT_CONNECTED. Availability is ACCOUNT-level (the operator's connected apps) and deliberately independent of the worker's own app bindings, so it is NOT derivable from worker_get: a worker with no email app still reports email:true when the account has a mailbox connected, and a worker that uses Slack all day reports slack:false if nobody connected Slack for the account. These are the same predicates the create/update gate applies, with ONE exception: messaging is reported true when the account has ANY messaging provider connected, while the gate checks the specific provider you name in target.provider — so messaging:true can still 400 if the account's connected provider is not the one you ask for. Only a human can connect a missing app (at https://workerkit.ai → Connections) — agents cannot. Requires the readWorkers scope." +
452
+ description: "Which delivery channels this worker's operator can send through RIGHT NOW: {email, slack, msTeams, telegram, notion, discord, messaging, webhook}, each true or false (webhook is always true: a URL you supply needs no connected app). Call this BEFORE delivery_create and pick a channel that is true — creating on a false one is a guaranteed 400 CHANNEL_NOT_CONNECTED. Availability is ACCOUNT-level (the operator's connected apps) and deliberately independent of the worker's own app bindings, so it is NOT derivable from worker_get: a worker with no email app still reports email:true when the account has a mailbox connected, and a worker that uses Slack all day reports slack:false if nobody connected Slack for the account. These are the same predicates the create/update gate applies, with ONE exception: messaging is reported true when the account has ANY messaging provider connected, while the gate checks the specific provider you name in target.provider — so messaging:true can still 400 if the account's connected provider is not the one you ask for. Only a human can connect a missing app (at https://workerkit.ai → Connections) — agents cannot. Requires the readWorkers scope." +
452
453
  DELIVERY_NOTE,
453
454
  auth: "manager",
454
455
  method: "get",
@@ -469,7 +470,7 @@ const createDelivery = {
469
470
  method: "post",
470
471
  schema: {
471
472
  tokenId: z.number().int().min(1).describe(TOKEN_ID_HINT),
472
- channel: z.enum(["email", "slack", "msTeams", "telegram", "notion", "discord", "messaging"]).describe("Where to send. IMMUTABLE after creation — to change it, delete this destination and create another. Check delivery_channels first: a channel that reports false is a guaranteed 400 CHANNEL_NOT_CONNECTED."),
473
+ channel: z.enum(["email", "slack", "msTeams", "telegram", "notion", "discord", "messaging", "webhook"]).describe("Where to send. IMMUTABLE after creation — to change it, delete this destination and create another. Check delivery_channels first: a channel that reports false is a guaranteed 400 CHANNEL_NOT_CONNECTED. webhook needs no connected app: the response carries signingSecret ONCE (verify X-PortEden-Signature with it — v1=hex(HMAC-SHA256 of '{X-PortEden-Timestamp}.{raw body}'); dedupe on X-PortEden-Event-Id), and the body is the typed run event, not rendered text."),
473
474
  title: z.string().max(120).optional().describe("Optional display title for the destination (≤120 chars)."),
474
475
  condition: z.enum(["always", "successOnly", "failureOnly"]).default("always").describe(DELIVERY_CONDITION_HINT),
475
476
  contentMode: z.enum(["full", "summary"]).default("full").describe(DELIVERY_CONTENT_MODE_HINT),
@@ -519,6 +520,21 @@ const updateDelivery = {
519
520
  }),
520
521
  annotations: UPDATE,
521
522
  };
523
+ const rotateDeliverySecret = {
524
+ name: "delivery_secret_rotate",
525
+ title: "Rotate Webhook Signing Secret",
526
+ description: "Mint a NEW signing secret for a WEBHOOK destination and get it back ONCE, in signingSecret (400 for any other channel — only webhooks sign). The previous secret stops verifying immediately, so update the receiver first or accept a gap. Every read (delivery_list) carries only signingSecretPrefix, never the secret: a caller that loses it rotates again. This is also how a CLONED webhook destination is armed — a clone arrives disabled with no secret, and delivery_update refuses to enable it until one is minted here. Requires the manageDeliveries scope." +
527
+ NEW_SCOPE_NOTE +
528
+ DELIVERY_NOTE,
529
+ auth: "manager",
530
+ method: "post",
531
+ schema: {
532
+ tokenId: z.number().int().min(1).describe(TOKEN_ID_HINT),
533
+ deliveryId: z.number().int().min(1).describe("The destination's id, as returned by delivery_list or delivery_create."),
534
+ },
535
+ path: (params) => `${API}/${params.tokenId}/deliveries/${params.deliveryId}/secret/rotate`,
536
+ annotations: UPDATE,
537
+ };
522
538
  const deleteDelivery = {
523
539
  name: "delivery_delete",
524
540
  title: "Delete Delivery Destination",
@@ -700,7 +716,7 @@ const kitInstall = {
700
716
  // chat message is a different power and stays on the dashboard and the kit
701
717
  // pipeline, where a manifest gets reviewed. All three need the createWorkers
702
718
  // scope.
703
- const CLONE_NOTE = " What a clone is: a copy of the source's PERMISSIONS (so it can never hold access a human did not already approve on the source) plus, by flag, the instruction, owner-authored memory, schedules, delivery destinations and the hosted deployment. Three things are deliberately NOT straight copies — schedules arrive DISABLED so a new worker never starts firing by itself (enable them with schedule_update once you are satisfied), AGENT-AUTHORED FACTS ARE NOT COPIED (only owner-authored rules and facts travel: what a worker learned about itself stays with the worker that learned it), and a webhook delivery destination arrives DISABLED WITH NO SIGNING SECRET so revoking the clone can never break its source (a human rotates one to arm it). The clone also inherits the SAME per-run and per-day spend ceilings, so cloning multiplies the fleet's ceiling — read fleet_budget_get before a bulk clone. Instruction history does not travel: the clone starts at version 1 with the text it was given. A source whose instruction came from a PROTECTED kit is refused while includeInstruction is true — that text belongs to the kit's publisher, so install the kit again or clone with includeInstruction:false.";
719
+ const CLONE_NOTE = " What a clone is: a copy of the source's PERMISSIONS (so it can never hold access a human did not already approve on the source) plus, by flag, the instruction, owner-authored memory, schedules, delivery destinations and the hosted deployment. Three things are deliberately NOT straight copies — schedules arrive DISABLED so a new worker never starts firing by itself (enable them with schedule_update once you are satisfied), AGENT-AUTHORED FACTS ARE NOT COPIED (only owner-authored rules and facts travel: what a worker learned about itself stays with the worker that learned it), and a webhook delivery destination arrives DISABLED WITH NO SIGNING SECRET so revoking the clone can never break its source (mint one with delivery_secret_rotate, then enable it with delivery_update; enabling it before that is refused). The clone also inherits the SAME per-run and per-day spend ceilings, so cloning multiplies the fleet's ceiling — read fleet_budget_get before a bulk clone. Instruction history does not travel: the clone starts at version 1 with the text it was given. A source whose instruction came from a PROTECTED kit is refused while includeInstruction is true — that text belongs to the kit's publisher, so install the kit again or clone with includeInstruction:false.";
704
720
  const CLONE_TITLE_HINT = "The new worker's title, 1–120 chars. Required: a clone is never silently named after its source.";
705
721
  // One shape, three tools: worker_clone and worker_clone_preview take it as their
706
722
  // body, worker_clone_bulk takes an array of it. Every include flag defaults to
@@ -804,29 +820,33 @@ const getWorkerBudget = {
804
820
  const setWorkerBudget = {
805
821
  name: "budget_set",
806
822
  title: "Set Worker Budget",
807
- description: "Change one worker's ceilings, and get the whole budget back as it now stands. PARTIAL: every field is optional and an OMITTED FIELD MEANS UNCHANGED, so you can raise one ceiling without restating the rest and without racing another writer's edit to a different field. Three traps. Setting maxUsdPerRun or maxUsdPerDay on a worker with NO HOSTED DEPLOYMENT is a 400 — there is nothing for a dollar cap to bind to, and budget_get's hasDeployment says which workers those are; maxRunsPerDay, by contrast, can be set on any worker. There is NO way to clear maxRunsPerDay back to the platform default here, because null already means 'leave it alone': send an explicit number instead. And LOWERING maxUsdPerRun below what a run needs does not fail loudly — it makes that worker's next run a Skipped receipt with a skipReason, which is a receipt an agent must go and read. maxConcurrentRuns is not settable (fixed at 1) and is absent from this call by design. A ZERO dollar cap means STOP, not unlimited. Requires the manageBudgets scope." +
823
+ description: "Change one worker's ceilings, and get the whole budget back as it now stands. PARTIAL: every field is optional and an OMITTED FIELD MEANS UNCHANGED, so you can raise one ceiling without restating the rest and without racing another writer's edit to a different field. Three traps. Setting maxUsdPerRun or maxUsdPerDay on a worker with NO HOSTED DEPLOYMENT is a 400 — there is nothing for a dollar cap to bind to, and budget_get's hasDeployment says which workers those are; maxRunsPerDay, by contrast, can be set on any worker. There is NO way to clear maxRunsPerDay back to the platform default here, because null already means 'leave it alone': send an explicit number instead. And LOWERING maxUsdPerRun below what a run needs does not fail loudly — it makes that worker's next run a Skipped receipt with a skipReason, which is a receipt an agent must go and read. maxConcurrentRuns is not settable (fixed at 1) and is absent from this call by design. A ZERO per-run cap is REFUSED (400), as is a day cap below the per-run cap: a zero reserve is a run that cannot start, not a worker that spends nothing — to stop a worker, use worker_set_enabled. maxRunsPerDay is clamped to the organization policy ceiling when one is enforced. The same call sets the question timeout: awaitInputTimeoutMinutes (how long a question the worker asks stays open; clearAwaitInputTimeout removes the bound). When it passes the question is closed out, never resumed with no answer, and a late answer still starts the run. Requires the manageBudgets scope." +
808
824
  BUDGET_WINDOW_NOTE +
809
825
  NEW_SCOPE_NOTE,
810
826
  auth: "manager",
811
827
  method: "patch",
812
828
  schema: {
813
829
  tokenId: z.number().int().min(1).describe(TOKEN_ID_HINT),
814
- maxUsdPerRun: z.number().min(0).max(1000).optional().describe("Hard per-run ceiling in USD, 0–1000 — also the amount reserved from the wallet at dispatch. Needs a hosted deployment. Omit to keep."),
815
- maxUsdPerDay: z.number().min(0).max(100000).optional().describe("Ceiling on this worker's reserved spend per UTC day, 0–100000. Needs a hosted deployment. Omit to keep."),
830
+ maxUsdPerRun: z.number().min(0.01).max(1000).optional().describe("Hard per-run ceiling in USD, 0.01–1000 — also the amount a platform-billed run reserves from the wallet at dispatch. Must stay at or below maxUsdPerDay. Needs a hosted deployment. Omit to keep."),
831
+ maxUsdPerDay: z.number().min(0.01).max(100000).optional().describe("Ceiling on this worker's RESERVED spend per UTC day, 0.01–100000 (each dispatch charges the full maxUsdPerRun against it). Must be at least maxUsdPerRun. Needs a hosted deployment. Omit to keep."),
816
832
  maxRunsPerDay: z.number().int().min(1).max(10000).optional().describe("Runs per the worker's own day, 1–10000. Omit to keep — there is no value here that restores the platform default."),
833
+ awaitInputTimeoutMinutes: z.number().int().min(1).max(43200).optional().describe("Minutes a question the worker asks (status AwaitingInput) stays open before the platform closes it out, 1–43200. Omit to keep; use clearAwaitInputTimeout to remove the bound (it then waits indefinitely). Needs a hosted deployment."),
834
+ clearAwaitInputTimeout: z.boolean().optional().describe("true = REMOVE the question timeout so a question waits indefinitely. Wins over awaitInputTimeoutMinutes if both are sent."),
817
835
  },
818
836
  path: (params) => `${API}/${params.tokenId}/budget`,
819
837
  bodyBuilder: (params) => ({
820
838
  maxUsdPerRun: params.maxUsdPerRun,
821
839
  maxUsdPerDay: params.maxUsdPerDay,
822
840
  maxRunsPerDay: params.maxRunsPerDay,
841
+ awaitInputTimeoutMinutes: params.awaitInputTimeoutMinutes,
842
+ clearAwaitInputTimeout: params.clearAwaitInputTimeout,
823
843
  }),
824
844
  annotations: UPDATE,
825
845
  };
826
846
  const getFleetBudget = {
827
847
  name: "fleet_budget_get",
828
848
  title: "Get Fleet Budget",
829
- description: "The ACCOUNT-WIDE spend ceiling and what has been spent against it — the brake above the per-worker caps, which exists because per-worker caps DO NOT COMPOSE: eighteen workers each capped at a dollar a day is an eighteen-dollar-a-day fleet. Returns maxUsdPerDay and maxUsdPerMonth (each ABSENT when no ceiling of that kind is set — and note that a ceiling of ZERO MEANS STOP, not unlimited), autoPauseOnBreach, spentTodayUsd, spentThisMonthUsd, lastBreachAtUtc, lastBreachScope ('fleet.day' | 'fleet.month') and serverTimeUtc — compute headroom and when a window rolls against serverTimeUtc, never your own clock. The spend figures are SETTLED model spend plus BYOK platform fees, read from run receipts rather than the wallet ledger: top-ups, worker-assistant spend and app-usage billing are neither counted here nor governed by this ceiling, so this number will not match a wallet balance. A run is refused once spend REACHES the ceiling (>=, not >), arriving as a Skipped receipt whose errorCode is fleet_spend_cap. Account-scoped by the key itself, so it takes no worker id. Requires the manageBudgets scope." +
849
+ description: "The ACCOUNT-WIDE spend ceiling and what has been spent against it — the brake above the per-worker caps, which exists because per-worker caps DO NOT COMPOSE: eighteen workers each capped at a dollar a day is an eighteen-dollar-a-day fleet. Returns maxUsdPerDay and maxUsdPerMonth (each ABSENT when no ceiling of that kind is set — and note that a ceiling of ZERO MEANS STOP, not unlimited), spentTodayUsd, spentThisMonthUsd, reservedInFlightUsd, lastBreachAtUtc, lastBreachScope ('fleet.day' | 'fleet.month') and serverTimeUtc — compute headroom and when a window rolls against serverTimeUtc, never your own clock. The spend figures are SETTLED run spend (model cost on platform-billed runs, the platform fee on BYOK runs), read from run receipts rather than the wallet ledger: top-ups, worker-assistant spend and app-usage billing are neither counted here nor governed by this ceiling, so this number will not match a wallet balance. reservedInFlightUsd is what runs in flight currently hold, and the ceiling compares against settled PLUS reserved, so headroom is the ceiling minus both. A run is refused once that committed total REACHES the ceiling (>=, not >), arriving as a Skipped receipt whose skipReason is FleetSpendCap and errorCode fleet_spend_cap. Account-scoped by the key itself, so it takes no worker id. Requires the manageBudgets scope." +
830
850
  NEW_SCOPE_NOTE,
831
851
  auth: "manager",
832
852
  method: "get",
@@ -837,7 +857,7 @@ const getFleetBudget = {
837
857
  const setFleetBudget = {
838
858
  name: "fleet_budget_set",
839
859
  title: "Set Fleet Budget",
840
- description: "Set or clear the account-wide ceiling, and get the fleet budget back with fresh spend figures. ABSENT MEANS UNCHANGED, so REMOVING a ceiling needs the matching CLEAR FLAG — clearMaxUsdPerDay / clearMaxUsdPerMonth — because null cannot mean both 'leave it alone' and 'none'. Sending a value and its clear flag together is contradictory and CLEAR WINS: dropping a ceiling is the safer reading of a confused request than raising one. ZERO IS NOT UNLIMITED — a ceiling of 0 stops the whole fleet, because a run is refused once spend reaches it. autoPauseOnBreach is the loud one: when a ceiling trips it DISABLES EVERY WORKER ON THE ACCOUNT rather than only refusing the run that tripped it — each worker's own API key stops working and its schedules stop firing — and nothing turns them back on when the window rolls, so a human, or worker_set_enabled one worker at a time, has to start them again. It is off by default; turn it on deliberately, and tell the human you did. A breach raises its event once per window rather than on every refused run, and lastBreachAtUtc/lastBreachScope on the response are how you see that it happened. Requires the manageBudgets scope." +
860
+ description: "Set or clear the account-wide ceiling, and get the fleet budget back with fresh spend figures. ABSENT MEANS UNCHANGED, so REMOVING a ceiling needs the matching CLEAR FLAG — clearMaxUsdPerDay / clearMaxUsdPerMonth — because null cannot mean both 'leave it alone' and 'none'. Sending a value and its clear flag together is contradictory and CLEAR WINS: dropping a ceiling is the safer reading of a confused request than raising one. ZERO IS NOT UNLIMITED — a ceiling of 0 stops the whole fleet, because a run is refused once spend reaches it. The ceiling counts SETTLED spend PLUS the reserves of runs in flight (reservedInFlightUsd on fleet_budget_get), so a burst of runs admitted together cannot overshoot it. A breach only REFUSES runs: each is a Skipped receipt with skipReason FleetSpendCap and errorCode fleet_spend_cap — NOT DailySpendCap, which is one worker's own cap — nothing is paused, every worker's own API key keeps working, and the fleet runs again by itself when the window rolls or the ceiling is raised. Webhook destinations receive the breach as run.blocked carrying the ceiling under budget, once per worker per day; lastBreachAtUtc/lastBreachScope on the response are how you see that it happened. Requires the manageBudgets scope." +
841
861
  NEW_SCOPE_NOTE,
842
862
  auth: "manager",
843
863
  method: "patch",
@@ -846,7 +866,6 @@ const setFleetBudget = {
846
866
  clearMaxUsdPerDay: z.boolean().optional().describe("true = REMOVE the daily ceiling (no account-wide day limit). Wins over maxUsdPerDay if both are sent."),
847
867
  maxUsdPerMonth: z.number().min(0).max(1000000).optional().describe("Account-wide ceiling per UTC month, 0–1000000. Omit to keep; use clearMaxUsdPerMonth to remove it."),
848
868
  clearMaxUsdPerMonth: z.boolean().optional().describe("true = REMOVE the monthly ceiling. Wins over maxUsdPerMonth if both are sent."),
849
- autoPauseOnBreach: z.boolean().optional().describe("true = disable EVERY worker on the account when a ceiling trips, instead of only refusing the run that tripped it. Re-enabling is manual. Omit to keep."),
850
869
  },
851
870
  path: `${API}/fleet/budget`,
852
871
  bodyBuilder: (params) => ({
@@ -854,10 +873,465 @@ const setFleetBudget = {
854
873
  clearMaxUsdPerDay: params.clearMaxUsdPerDay,
855
874
  maxUsdPerMonth: params.maxUsdPerMonth,
856
875
  clearMaxUsdPerMonth: params.clearMaxUsdPerMonth,
857
- autoPauseOnBreach: params.autoPauseOnBreach,
858
876
  }),
859
877
  annotations: UPDATE,
860
878
  };
879
+ // ─── Permissions (read-only) ────────────────────────────────────────────────
880
+ const getWorkerPermissions = {
881
+ name: "worker_permissions_get",
882
+ title: "Get Worker Permissions",
883
+ 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.",
884
+ auth: "manager",
885
+ method: "get",
886
+ schema: {
887
+ tokenId: z.number().int().min(1).describe(TOKEN_ID_HINT),
888
+ },
889
+ path: (params) => `${API}/${params.tokenId}/permissions`,
890
+ paramFilter: () => ({}),
891
+ annotations: READ_ONLY,
892
+ };
893
+ // ─── Kits (authoring) ───────────────────────────────────────────────────────
894
+ //
895
+ // The publishKits lane. A PRIVATE kit installed with kit_install is how a worker
896
+ // is created from scratch on this surface (cloning is the only other non-kit
897
+ // path), so these descriptors teach the flow as much as the wire: read the guide
898
+ // and the vocabulary on the Directory server, validate, publish private, install.
899
+ const KIT_REF = z.string().min(1).max(120);
900
+ 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.";
901
+ 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." +
902
+ NEW_SCOPE_NOTE;
903
+ const kitRefParam = KIT_REF.describe(KIT_REF_HINT);
904
+ const kitRefPath = (params, suffix = "") => `${KITS_API}/${encodeURIComponent(String(params.kitRef))}${suffix}`;
905
+ const withoutKitRef = (params) => {
906
+ const { kitRef: _kitRef, ...body } = params;
907
+ return body;
908
+ };
909
+ const LISTING_SCHEMA = {
910
+ 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."),
911
+ jobSentence: z.string().min(1).max(200).describe("One-line outcome, 1-200 chars. With name, the ONLY free-text-searched field."),
912
+ description: z.string().max(20000).optional().describe("Markdown listing description (≤20000; write it under 500: one line, then 3-5 bullets). Not searched."),
913
+ 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."),
914
+ 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."),
915
+ isProtected: z.boolean().optional().describe("true = installers get a working worker and the permission manifest but never the instruction text. Independent of visibility."),
916
+ 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."),
917
+ 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'."),
918
+ recommendedClients: z.array(z.string().max(40)).max(10).optional().describe("≤10 client names, ≤40 chars each, e.g. claude, chatgpt, cursor."),
919
+ recommendedModel: z.string().max(80).optional().describe("Display-only recommended model name (≤80)."),
920
+ declaredModelFloor: z.string().max(80).optional().describe("Display-only minimum model (≤80)."),
921
+ 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."),
922
+ };
923
+ 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.");
924
+ const getMyPublisher = {
925
+ name: "publisher_get_mine",
926
+ title: "Get My Publisher",
927
+ 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.",
928
+ auth: "manager",
929
+ method: "get",
930
+ schema: {},
931
+ path: `${KITS_API}/publisher`,
932
+ annotations: READ_ONLY,
933
+ };
934
+ const setMyPublisher = {
935
+ name: "publisher_set",
936
+ title: "Update My Publisher",
937
+ 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." +
938
+ NEW_SCOPE_NOTE,
939
+ auth: "manager",
940
+ method: "put",
941
+ schema: {
942
+ name: z.string().min(3).max(80).optional().describe("New publisher name (3-80, globally unique)."),
943
+ description: z.string().max(2000).optional().describe("Bio shown on the publisher page."),
944
+ links: z.array(z.object({
945
+ kind: z.string().describe("Link kind, e.g. website, x, github, linkedin."),
946
+ value: z.string().describe("The handle or URL for that kind."),
947
+ })).optional().describe("Full replace of the link list."),
948
+ isListed: z.boolean().optional().describe("false hides the profile page from the directory; kits stay installable."),
949
+ },
950
+ path: `${KITS_API}/publisher`,
951
+ bodyBuilder: (params) => ({
952
+ name: params.name,
953
+ description: params.description,
954
+ links: params.links,
955
+ isListed: params.isListed,
956
+ }),
957
+ annotations: UPDATE,
958
+ };
959
+ const listMyKits = {
960
+ name: "my_kits_list",
961
+ title: "List My Kits",
962
+ 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.",
963
+ auth: "manager",
964
+ method: "get",
965
+ schema: {},
966
+ path: `${KITS_API}/mine`,
967
+ annotations: READ_ONLY,
968
+ };
969
+ const validateKit = {
970
+ name: "kit_validate",
971
+ 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." +
973
+ AUTHORING_FLOW_NOTE,
974
+ auth: "manager",
975
+ method: "post",
976
+ schema: {
977
+ ...LISTING_SCHEMA,
978
+ name: LISTING_SCHEMA.name.optional(),
979
+ jobSentence: LISTING_SCHEMA.jobSentence.optional(),
980
+ categorySlugs: LISTING_SCHEMA.categorySlugs.optional(),
981
+ content: CONTENT_SCHEMA,
982
+ 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."),
983
+ },
984
+ path: (params) => params.kitRef ? `${KITS_API}/validate?kitRef=${encodeURIComponent(String(params.kitRef))}` : `${KITS_API}/validate`,
985
+ bodyBuilder: withoutKitRef,
986
+ annotations: READ_ONLY,
987
+ };
988
+ const publishKit = {
989
+ name: "kit_publish",
990
+ 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." +
992
+ AUTHORING_FLOW_NOTE,
993
+ auth: "manager",
994
+ method: "post",
995
+ schema: {
996
+ ...LISTING_SCHEMA,
997
+ content: CONTENT_SCHEMA.optional(),
998
+ 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."),
999
+ },
1000
+ path: KITS_API,
1001
+ annotations: CREATE,
1002
+ };
1003
+ const updateKit = {
1004
+ name: "kit_update",
1005
+ title: "Update Kit",
1006
+ 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." +
1007
+ NEW_SCOPE_NOTE,
1008
+ auth: "manager",
1009
+ method: "patch",
1010
+ schema: {
1011
+ kitRef: kitRefParam,
1012
+ name: LISTING_SCHEMA.name.optional(),
1013
+ jobSentence: LISTING_SCHEMA.jobSentence.optional(),
1014
+ description: LISTING_SCHEMA.description,
1015
+ categorySlugs: LISTING_SCHEMA.categorySlugs.optional(),
1016
+ appDescriptions: LISTING_SCHEMA.appDescriptions,
1017
+ recommendedClients: LISTING_SCHEMA.recommendedClients,
1018
+ recommendedModel: LISTING_SCHEMA.recommendedModel,
1019
+ declaredModelFloor: LISTING_SCHEMA.declaredModelFloor,
1020
+ modelScores: LISTING_SCHEMA.modelScores,
1021
+ isProtected: LISTING_SCHEMA.isProtected,
1022
+ instructionContent: z.string().min(1).max(100000).optional().describe("Replace the served instruction (1-100000)."),
1023
+ whenToUse: z.string().max(500).optional().describe("Replace the when-to-use line (≤500; '' clears)."),
1024
+ startCommand: z.string().max(20000).optional().describe("Replace the /start command (≤20000; '' clears)."),
1025
+ endCommand: z.string().max(20000).optional().describe("Replace the /end command (≤20000; '' clears)."),
1026
+ endCommandDescription: z.string().max(500).optional().describe("Replace when the worker should call /end (≤500; '' clears)."),
1027
+ 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."),
1028
+ permissions: z.array(z.record(z.unknown())).optional().describe("Full replace of the pinned-app permission selections (content.apps shape). Replaces TOGETHER with categorySlots."),
1029
+ categorySlots: z.array(z.record(z.unknown())).optional().describe("Full replace of the capability slots (content.categorySlots shape). Replaces TOGETHER with permissions; [] clears."),
1030
+ contactAllowAll: z.boolean().optional().describe("The token-wide contact/board stance: true allow-all, false deny-by-default."),
1031
+ memoryProfile: z.enum(["stateless", "contextual"]).optional().describe("The declared worker class."),
1032
+ selfFactsEnabled: z.boolean().optional().describe("Whether the instruction expects the worker to save its own facts."),
1033
+ 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'."),
1034
+ },
1035
+ path: (params) => kitRefPath(params),
1036
+ bodyBuilder: withoutKitRef,
1037
+ annotations: UPDATE,
1038
+ };
1039
+ const replaceKit = {
1040
+ name: "kit_replace",
1041
+ 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." +
1043
+ NEW_SCOPE_NOTE,
1044
+ auth: "manager",
1045
+ method: "put",
1046
+ schema: {
1047
+ kitRef: kitRefParam,
1048
+ ...LISTING_SCHEMA,
1049
+ content: CONTENT_SCHEMA,
1050
+ },
1051
+ path: (params) => kitRefPath(params),
1052
+ bodyBuilder: withoutKitRef,
1053
+ annotations: UPDATE,
1054
+ };
1055
+ const getKitScan = {
1056
+ name: "kit_scan_get",
1057
+ title: "Get Kit Scan Report",
1058
+ 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.",
1059
+ auth: "manager",
1060
+ method: "get",
1061
+ schema: { kitRef: kitRefParam },
1062
+ path: (params) => kitRefPath(params, "/scan"),
1063
+ paramFilter: () => ({}),
1064
+ annotations: READ_ONLY,
1065
+ };
1066
+ const unpublishKit = {
1067
+ name: "kit_unpublish",
1068
+ title: "Unpublish Kit",
1069
+ 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." +
1070
+ NEW_SCOPE_NOTE,
1071
+ auth: "manager",
1072
+ method: "post",
1073
+ schema: { kitRef: kitRefParam },
1074
+ path: (params) => kitRefPath(params, "/unpublish"),
1075
+ bodyBuilder: () => ({}),
1076
+ annotations: UPDATE,
1077
+ };
1078
+ const relistKit = {
1079
+ name: "kit_relist",
1080
+ title: "Relist Kit",
1081
+ 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." +
1082
+ NEW_SCOPE_NOTE,
1083
+ auth: "manager",
1084
+ method: "post",
1085
+ schema: { kitRef: kitRefParam },
1086
+ path: (params) => kitRefPath(params, "/relist"),
1087
+ bodyBuilder: () => ({}),
1088
+ annotations: UPDATE,
1089
+ };
1090
+ const makeKitPrivate = {
1091
+ name: "kit_make_private",
1092
+ title: "Make Kit Private",
1093
+ 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." +
1094
+ NEW_SCOPE_NOTE,
1095
+ auth: "manager",
1096
+ method: "post",
1097
+ schema: { kitRef: kitRefParam },
1098
+ path: (params) => kitRefPath(params, "/make-private"),
1099
+ bodyBuilder: () => ({}),
1100
+ annotations: UPDATE,
1101
+ };
1102
+ const deleteKit = {
1103
+ name: "kit_delete",
1104
+ title: "Delete Kit",
1105
+ 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." +
1106
+ NEW_SCOPE_NOTE,
1107
+ auth: "manager",
1108
+ method: "delete",
1109
+ schema: { kitRef: kitRefParam },
1110
+ path: (params) => kitRefPath(params),
1111
+ successMessage: "Kit deleted.",
1112
+ annotations: DELETE,
1113
+ };
1114
+ // ─── Connected apps + model keys (manageConnections) ────────────────────────
1115
+ // The operational half of authoring: a worker can only use apps that are
1116
+ // connected on its operator. The read is in the kit vocabulary (the same app
1117
+ // codes content.apps[].code takes) so a kit's apps can be checked against it
1118
+ // verbatim; the write takes a credential, validated live and never returned.
1119
+ const APPS_API = "/api/manage/apps";
1120
+ const MODEL_KEYS_API = "/api/manage/model-keys";
1121
+ 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.";
1122
+ 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.";
1123
+ const listApps = {
1124
+ name: "apps_list",
1125
+ title: "List Connected Apps",
1126
+ 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.",
1127
+ auth: "manager",
1128
+ method: "get",
1129
+ schema: {
1130
+ operatorId: z.string().uuid().optional().describe(OPERATOR_ID_HINT),
1131
+ },
1132
+ path: APPS_API,
1133
+ annotations: READ_ONLY,
1134
+ };
1135
+ const connectApp = {
1136
+ name: "app_connect",
1137
+ title: "Connect App",
1138
+ 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." +
1139
+ NEW_SCOPE_NOTE,
1140
+ auth: "manager",
1141
+ method: "post",
1142
+ schema: {
1143
+ provider: z.string().min(1).max(120).describe(PROVIDER_HINT),
1144
+ 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 {}."),
1145
+ operatorId: z.string().uuid().optional().describe(OPERATOR_ID_HINT),
1146
+ label: z.string().max(200).optional().describe("Optional operator-facing label, where the family keeps one (messaging, web search, keys)."),
1147
+ },
1148
+ path: (params) => `${APPS_API}/${encodeURIComponent(String(params.provider))}/connect`,
1149
+ bodyBuilder: (params) => ({ operatorId: params.operatorId, label: params.label, credential: params.credential }),
1150
+ annotations: CREATE,
1151
+ };
1152
+ const disconnectApp = {
1153
+ name: "app_disconnect",
1154
+ title: "Disconnect App",
1155
+ 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." +
1156
+ NEW_SCOPE_NOTE,
1157
+ auth: "manager",
1158
+ method: "delete",
1159
+ schema: {
1160
+ provider: z.string().min(1).max(120).describe(PROVIDER_HINT),
1161
+ 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)."),
1162
+ operatorId: z.string().uuid().optional().describe(OPERATOR_ID_HINT),
1163
+ },
1164
+ // DELETE sends no body, so the optional operator rides the query string (the same promotion
1165
+ // kit_validate uses for kitRef).
1166
+ path: (params) => `${APPS_API}/${encodeURIComponent(String(params.provider))}/connections/${encodeURIComponent(String(params.connectionId))}` +
1167
+ (typeof params.operatorId === "string" && params.operatorId ? `?operatorId=${encodeURIComponent(params.operatorId)}` : ""),
1168
+ successMessage: "Connection removed.",
1169
+ annotations: DELETE,
1170
+ };
1171
+ const MODEL_PROVIDER_HINT = "The model provider: Anthropic | OpenAI | Google | XAI (case-insensitive).";
1172
+ const listModelKeys = {
1173
+ name: "model_keys_list",
1174
+ title: "List Model Keys",
1175
+ 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.",
1176
+ auth: "manager",
1177
+ method: "get",
1178
+ schema: {
1179
+ includeRevoked: z.boolean().default(false).describe("true = include keys that were removed."),
1180
+ },
1181
+ path: MODEL_KEYS_API,
1182
+ annotations: READ_ONLY,
1183
+ };
1184
+ const setModelKey = {
1185
+ name: "model_key_set",
1186
+ title: "Set Model Key",
1187
+ 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." +
1188
+ NEW_SCOPE_NOTE,
1189
+ auth: "manager",
1190
+ method: "put",
1191
+ schema: {
1192
+ provider: z.string().min(1).max(40).describe(MODEL_PROVIDER_HINT),
1193
+ apiKey: z.string().min(8).max(4096).describe("The provider API key, verbatim."),
1194
+ label: z.string().max(200).optional().describe("Optional label shown beside the key."),
1195
+ },
1196
+ path: (params) => `${MODEL_KEYS_API}/${encodeURIComponent(String(params.provider))}`,
1197
+ bodyBuilder: (params) => ({ apiKey: params.apiKey, label: params.label }),
1198
+ annotations: UPDATE,
1199
+ };
1200
+ const deleteModelKey = {
1201
+ name: "model_key_delete",
1202
+ title: "Delete Model Key",
1203
+ 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." +
1204
+ NEW_SCOPE_NOTE,
1205
+ auth: "manager",
1206
+ method: "delete",
1207
+ schema: {
1208
+ provider: z.string().min(1).max(40).describe(MODEL_PROVIDER_HINT),
1209
+ },
1210
+ path: (params) => `${MODEL_KEYS_API}/${encodeURIComponent(String(params.provider))}`,
1211
+ successMessage: "Model key removed.",
1212
+ annotations: DELETE,
1213
+ };
1214
+ // ─── Custom MCP servers — the account's own MCP apps ────────────────────────
1215
+ //
1216
+ // How an app the platform does not offer reaches a worker: register its MCP
1217
+ // server (the credential rides the same call), discover its tools, enable the
1218
+ // ones a job needs — which publishes it — and bind it in a kit's
1219
+ // content.mcpServers by id. The same steps the Kit Creator Studio runs; here a
1220
+ // machine runs them with the secret in hand.
1221
+ const MCP_SERVERS_API = "/api/manage/mcp-servers";
1222
+ 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.";
1223
+ const MCP_AUTH_TYPES = ["None", "Bearer", "ApiKeyHeader", "ApiKeyQuery", "Basic", "CustomHeaders", "McpOAuth"];
1224
+ const listMcpServers = {
1225
+ name: "mcp_servers_list",
1226
+ title: "List MCP Servers",
1227
+ 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.",
1228
+ auth: "manager",
1229
+ method: "get",
1230
+ schema: {},
1231
+ path: MCP_SERVERS_API,
1232
+ annotations: READ_ONLY,
1233
+ };
1234
+ const getMcpServer = {
1235
+ name: "mcp_server_get",
1236
+ title: "Get MCP Server",
1237
+ 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.",
1238
+ auth: "manager",
1239
+ method: "get",
1240
+ schema: {
1241
+ gatewayId: z.string().uuid().describe(GATEWAY_ID_HINT),
1242
+ },
1243
+ path: (params) => `${MCP_SERVERS_API}/${encodeURIComponent(String(params.gatewayId))}`,
1244
+ paramFilter: () => ({}),
1245
+ annotations: READ_ONLY,
1246
+ };
1247
+ const createMcpServer = {
1248
+ name: "mcp_server_create",
1249
+ title: "Register MCP Server",
1250
+ 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." +
1251
+ NEW_SCOPE_NOTE,
1252
+ auth: "manager",
1253
+ method: "post",
1254
+ schema: {
1255
+ name: z.string().min(1).max(200).describe("Display name, e.g. the product name."),
1256
+ upstreamUrl: z.string().min(8).max(2048).describe("The https MCP endpoint, verbatim from the user or the vendor's docs."),
1257
+ authType: z.enum(MCP_AUTH_TYPES).default("None").describe("What the server expects. Default None."),
1258
+ credentialScope: z.enum(["operator", "account"]).default("operator").describe("operator = each operator connects its own credential (default) | account = one credential for every operator."),
1259
+ 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."),
1260
+ 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."),
1261
+ description: z.string().max(1000).optional().describe("Catalog blurb shown where the server is picked."),
1262
+ 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)."),
1263
+ credentialHelpText: z.string().max(500).optional().describe("A one-line hint for the credential field, e.g. 'Personal API key'."),
1264
+ setupGuideUrl: z.string().max(2048).optional().describe("The vendor's setup page."),
1265
+ slug: z.string().max(60).optional().describe("Override the slug derived from the name (uppercase, unique within the account)."),
1266
+ namespacePrefix: z.string().max(20).optional().describe("Override the tool-name prefix derived from the slug ([a-z0-9_], ≤20)."),
1267
+ transport: z.enum(["AutoDetect", "StreamableHttp", "Sse"]).optional().describe("Default AutoDetect."),
1268
+ oauthScopes: z.string().max(2048).optional().describe("McpOAuth only: space-delimited scopes to request."),
1269
+ callTimeoutSeconds: z.number().int().min(1).max(600).optional().describe("Per-call timeout; default 60."),
1270
+ },
1271
+ path: MCP_SERVERS_API,
1272
+ bodyBuilder: (params) => ({
1273
+ name: params.name,
1274
+ upstreamUrl: params.upstreamUrl,
1275
+ authType: params.authType,
1276
+ credentialScope: params.credentialScope,
1277
+ credential: params.credential,
1278
+ operatorId: params.operatorId,
1279
+ description: params.description,
1280
+ connectionInstructions: params.connectionInstructions,
1281
+ credentialHelpText: params.credentialHelpText,
1282
+ setupGuideUrl: params.setupGuideUrl,
1283
+ slug: params.slug,
1284
+ namespacePrefix: params.namespacePrefix,
1285
+ transport: params.transport,
1286
+ oAuthScopes: params.oauthScopes,
1287
+ callTimeoutSeconds: params.callTimeoutSeconds,
1288
+ }),
1289
+ annotations: CREATE,
1290
+ };
1291
+ const discoverMcpServer = {
1292
+ name: "mcp_server_discover",
1293
+ title: "Discover MCP Server Tools",
1294
+ 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." +
1295
+ NEW_SCOPE_NOTE,
1296
+ auth: "manager",
1297
+ method: "post",
1298
+ schema: {
1299
+ gatewayId: z.string().uuid().describe(GATEWAY_ID_HINT),
1300
+ operatorId: z.string().uuid().optional().describe("Scope operator: whose stored credential to list with. Omit for the account's default operator."),
1301
+ },
1302
+ path: (params) => `${MCP_SERVERS_API}/${encodeURIComponent(String(params.gatewayId))}/discover`,
1303
+ bodyBuilder: (params) => ({ operatorId: params.operatorId }),
1304
+ annotations: UPDATE,
1305
+ };
1306
+ const setMcpServerTools = {
1307
+ name: "mcp_server_set_tools",
1308
+ title: "Set MCP Server Tools",
1309
+ 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." +
1310
+ NEW_SCOPE_NOTE,
1311
+ auth: "manager",
1312
+ method: "put",
1313
+ schema: {
1314
+ gatewayId: z.string().uuid().describe(GATEWAY_ID_HINT),
1315
+ enabledToolIds: z.array(z.number().int().nonnegative()).max(500).describe("The complete set of toolId values to enable (from tools[]); [] unpublishes the server."),
1316
+ },
1317
+ path: (params) => `${MCP_SERVERS_API}/${encodeURIComponent(String(params.gatewayId))}/tools`,
1318
+ bodyBuilder: (params) => ({ enabledToolIds: params.enabledToolIds }),
1319
+ annotations: UPDATE,
1320
+ };
1321
+ const deleteMcpServer = {
1322
+ name: "mcp_server_delete",
1323
+ title: "Delete MCP Server",
1324
+ 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." +
1325
+ NEW_SCOPE_NOTE,
1326
+ auth: "manager",
1327
+ method: "delete",
1328
+ schema: {
1329
+ gatewayId: z.string().uuid().describe(GATEWAY_ID_HINT),
1330
+ },
1331
+ path: (params) => `${MCP_SERVERS_API}/${encodeURIComponent(String(params.gatewayId))}`,
1332
+ successMessage: "MCP server deleted.",
1333
+ annotations: DELETE,
1334
+ };
861
1335
  // ─── Export ─────────────────────────────────────────────────────────────────
862
1336
  export const manageDescriptors = [
863
1337
  getKeyInfo,
@@ -886,6 +1360,7 @@ export const manageDescriptors = [
886
1360
  deliveryChannels,
887
1361
  createDelivery,
888
1362
  updateDelivery,
1363
+ rotateDeliverySecret,
889
1364
  deleteDelivery,
890
1365
  getInstruction,
891
1366
  setInstruction,
@@ -902,5 +1377,30 @@ export const manageDescriptors = [
902
1377
  setWorkerBudget,
903
1378
  getFleetBudget,
904
1379
  setFleetBudget,
1380
+ getWorkerPermissions,
1381
+ getMyPublisher,
1382
+ setMyPublisher,
1383
+ listMyKits,
1384
+ validateKit,
1385
+ publishKit,
1386
+ updateKit,
1387
+ replaceKit,
1388
+ getKitScan,
1389
+ unpublishKit,
1390
+ relistKit,
1391
+ makeKitPrivate,
1392
+ deleteKit,
1393
+ listApps,
1394
+ connectApp,
1395
+ disconnectApp,
1396
+ listModelKeys,
1397
+ setModelKey,
1398
+ deleteModelKey,
1399
+ listMcpServers,
1400
+ getMcpServer,
1401
+ createMcpServer,
1402
+ discoverMcpServer,
1403
+ setMcpServerTools,
1404
+ deleteMcpServer,
905
1405
  ];
906
1406
  //# sourceMappingURL=manage.js.map