skedyul 1.7.30 → 1.7.31

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/dist/cli/index.js CHANGED
@@ -3646,6 +3646,14 @@ var AppAuthInvalidError = class extends Error {
3646
3646
  this.name = "AppAuthInvalidError";
3647
3647
  }
3648
3648
  };
3649
+ var TokenRefreshRequiredError = class extends Error {
3650
+ constructor(message = "Access token expired", options) {
3651
+ super(message);
3652
+ this.code = "TOKEN_REFRESH_REQUIRED";
3653
+ this.name = "TokenRefreshRequiredError";
3654
+ this.tokenKey = options?.tokenKey;
3655
+ }
3656
+ };
3649
3657
 
3650
3658
  // src/ratelimit/errors.ts
3651
3659
  var RateLimitExceededError = class extends Error {
@@ -3961,6 +3969,24 @@ function createCallToolHandler(registry, state, onMaxRequests) {
3961
3969
  }
3962
3970
  };
3963
3971
  }
3972
+ if (error instanceof TokenRefreshRequiredError) {
3973
+ return {
3974
+ success: false,
3975
+ output: null,
3976
+ billing: { credits: 0 },
3977
+ meta: {
3978
+ success: false,
3979
+ message: error.message,
3980
+ toolName
3981
+ },
3982
+ error: {
3983
+ code: error.code,
3984
+ message: error.message,
3985
+ category: "auth",
3986
+ ...error.tokenKey ? { details: { tokenKey: error.tokenKey } } : {}
3987
+ }
3988
+ };
3989
+ }
3964
3990
  if (error instanceof AppAuthInvalidError) {
3965
3991
  return {
3966
3992
  output: null,
@@ -4714,6 +4740,76 @@ async function handleOAuthCallback(parsedBody, hooks) {
4714
4740
  }
4715
4741
  }
4716
4742
 
4743
+ // src/server/handlers/refresh-token-handler.ts
4744
+ async function handleRefreshToken(parsedBody, hooks) {
4745
+ if (!hooks?.refresh_token) {
4746
+ return {
4747
+ status: 404,
4748
+ body: { error: "Refresh token handler not configured" }
4749
+ };
4750
+ }
4751
+ const body = parsedBody ?? {};
4752
+ if (!body.env || typeof body.env !== "object" || Array.isArray(body.env)) {
4753
+ return {
4754
+ status: 400,
4755
+ body: {
4756
+ error: {
4757
+ code: -32602,
4758
+ message: "Missing envelope format: expected { env }"
4759
+ }
4760
+ }
4761
+ };
4762
+ }
4763
+ const refreshContext = {
4764
+ env: body.env,
4765
+ invocation: body.invocation,
4766
+ log: createContextLogger()
4767
+ };
4768
+ const requestConfig = {
4769
+ baseUrl: body.env.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
4770
+ apiToken: body.env.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
4771
+ };
4772
+ try {
4773
+ const refreshHook = hooks.refresh_token;
4774
+ const refreshHandler = typeof refreshHook === "function" ? refreshHook : refreshHook.handler;
4775
+ const result = await withRequestEnv(body.env, async () => {
4776
+ return await runWithConfig(requestConfig, async () => {
4777
+ return await runWithLogContext({ invocation: body.invocation }, async () => {
4778
+ return await refreshHandler(refreshContext);
4779
+ });
4780
+ });
4781
+ });
4782
+ return {
4783
+ status: 200,
4784
+ body: {
4785
+ env: result.env ?? {}
4786
+ }
4787
+ };
4788
+ } catch (err) {
4789
+ if (err instanceof AppAuthInvalidError) {
4790
+ return {
4791
+ status: 401,
4792
+ body: {
4793
+ error: {
4794
+ code: err.code,
4795
+ message: err.message
4796
+ }
4797
+ }
4798
+ };
4799
+ }
4800
+ const errorMessage = err instanceof Error ? err.message : String(err ?? "Unknown error");
4801
+ return {
4802
+ status: 500,
4803
+ body: {
4804
+ error: {
4805
+ code: -32603,
4806
+ message: errorMessage
4807
+ }
4808
+ }
4809
+ };
4810
+ }
4811
+ }
4812
+
4717
4813
  // src/types/webhook.ts
4718
4814
  function isRuntimeWebhookContext(ctx) {
4719
4815
  return "appInstallationId" in ctx && ctx.appInstallationId !== void 0;
@@ -4817,6 +4913,33 @@ async function executeWebhookHandler(handle, webhookRegistry, data) {
4817
4913
  });
4818
4914
  });
4819
4915
  } catch (err) {
4916
+ if (err instanceof TokenRefreshRequiredError) {
4917
+ return {
4918
+ status: 200,
4919
+ body: {
4920
+ success: false,
4921
+ error: {
4922
+ code: err.code,
4923
+ message: err.message,
4924
+ category: "auth",
4925
+ ...err.tokenKey ? { details: { tokenKey: err.tokenKey } } : {}
4926
+ }
4927
+ }
4928
+ };
4929
+ }
4930
+ if (err instanceof AppAuthInvalidError) {
4931
+ return {
4932
+ status: 200,
4933
+ body: {
4934
+ success: false,
4935
+ error: {
4936
+ code: err.code,
4937
+ message: err.message,
4938
+ category: "auth"
4939
+ }
4940
+ }
4941
+ };
4942
+ }
4820
4943
  console.error(`Webhook handler '${handle}' error:`, err);
4821
4944
  return {
4822
4945
  status: 500,
@@ -5019,6 +5142,14 @@ async function handleOAuthCallbackRoute(req, ctx) {
5019
5142
  const result = await handleOAuthCallback(parseResult.data, ctx.config.hooks);
5020
5143
  return { status: result.status, body: result.body };
5021
5144
  }
5145
+ async function handleRefreshTokenRoute(req, ctx) {
5146
+ const parseResult = parseJsonBody(req);
5147
+ if (!parseResult.success) {
5148
+ return parseResult.error;
5149
+ }
5150
+ const result = await handleRefreshToken(parseResult.data, ctx.config.hooks);
5151
+ return { status: result.status, body: result.body };
5152
+ }
5022
5153
  async function handleWebhookRoute(req, handle, ctx) {
5023
5154
  if (!ctx.webhookRegistry) {
5024
5155
  return { status: 404, body: { error: `Webhook handler '${handle}' not found` } };
@@ -5510,6 +5641,21 @@ async function handleBatchOperationRoute(req, ctx) {
5510
5641
  { allowed: true, afterMs: err.retryAfterMs }
5511
5642
  );
5512
5643
  }
5644
+ if (err instanceof TokenRefreshRequiredError) {
5645
+ return softFailureResponse({
5646
+ code: err.code,
5647
+ message: err.message,
5648
+ category: "auth",
5649
+ ...err.tokenKey ? { details: { tokenKey: err.tokenKey } } : {}
5650
+ });
5651
+ }
5652
+ if (err instanceof AppAuthInvalidError) {
5653
+ return softFailureResponse({
5654
+ code: err.code,
5655
+ message: err.message,
5656
+ category: "auth"
5657
+ });
5658
+ }
5513
5659
  log.error("Batch operation failed", err);
5514
5660
  return {
5515
5661
  status: 500,
@@ -5594,6 +5740,9 @@ async function routeRequest(req, ctx) {
5594
5740
  if (req.path === "/oauth_callback" && req.method === "POST") {
5595
5741
  return handleOAuthCallbackRoute(req, ctx);
5596
5742
  }
5743
+ if (req.path === "/refresh_token" && req.method === "POST") {
5744
+ return handleRefreshTokenRoute(req, ctx);
5745
+ }
5597
5746
  if (req.path === "/health" && req.method === "GET") {
5598
5747
  return handleHealthRoute(ctx);
5599
5748
  }
@@ -10632,7 +10781,16 @@ var SkillYAMLSchema = import_v47.z.object({
10632
10781
  tools: SkillToolsSchema.optional(),
10633
10782
  // CRM context - specifies which models/fields to include in schema
10634
10783
  crmContext: CRMContextSchema.optional(),
10784
+ /**
10785
+ * Entity field handles this skill is allowed to write while loaded.
10786
+ * Writing outside this list is reported as an unowned write.
10787
+ */
10635
10788
  ownedFields: import_v47.z.array(import_v47.z.string()).optional(),
10789
+ /**
10790
+ * Entity field handles this skill must have written before it replies.
10791
+ * Only handles mapped in the install's AppCrmMap are enforced.
10792
+ */
10793
+ requiredWrites: import_v47.z.array(import_v47.z.string()).optional(),
10636
10794
  // Quality evaluators owned by this skill
10637
10795
  evaluators: import_v47.z.array(SkillEvaluatorSchema).optional(),
10638
10796
  // Few-shot examples
@@ -10649,6 +10807,7 @@ var SkillYAMLV2Schema = import_v47.z.object({
10649
10807
  tools: import_v47.z.array(SkillToolDefinitionSchema).optional(),
10650
10808
  crmContext: CRMContextSchema.optional(),
10651
10809
  ownedFields: import_v47.z.array(import_v47.z.string()).optional(),
10810
+ requiredWrites: import_v47.z.array(import_v47.z.string()).optional(),
10652
10811
  evaluators: import_v47.z.array(SkillEvaluatorSchema).optional(),
10653
10812
  examples: import_v47.z.array(SkillExampleSchema).optional()
10654
10813
  });
@@ -10664,8 +10823,8 @@ var SkillRefSchema = import_v47.z.union([
10664
10823
  description: import_v47.z.string().optional(),
10665
10824
  // For AI SDK Agent Skills discovery
10666
10825
  /**
10667
- * @deprecated Ignored. The agent must call system:skill:load each turn.
10668
- * Kept so existing agent YAML still validates.
10826
+ * Pre-seed this skill as loaded before the first model step, so the agent
10827
+ * does not have to call system:skill:load for it.
10669
10828
  */
10670
10829
  alwaysLoad: import_v47.z.boolean().optional(),
10671
10830
  // Version selection (pick one):
@@ -10673,10 +10832,14 @@ var SkillRefSchema = import_v47.z.union([
10673
10832
  // Pin to specific version number
10674
10833
  versions: import_v47.z.array(SkillVersionWeightSchema).optional(),
10675
10834
  // A/B testing weights
10676
- // Legacy support:
10677
- instructions: import_v47.z.string().optional(),
10678
- // Inline instructions (deprecated)
10679
- enabled: import_v47.z.boolean().optional()
10835
+ /**
10836
+ * @deprecated Not implemented. Instructions come from the skill file.
10837
+ */
10838
+ instructions: import_v47.z.string().optional().describe("Not implemented"),
10839
+ /**
10840
+ * @deprecated Not implemented. Remove the skill from `skills` instead.
10841
+ */
10842
+ enabled: import_v47.z.boolean().optional().describe("Not implemented")
10680
10843
  })
10681
10844
  ]);
10682
10845
  var SkillMetadataSchema = import_v47.z.object({
@@ -10795,7 +10958,11 @@ var ContextValidationResultSchema = import_v48.z.object({
10795
10958
  issues: import_v48.z.array(ContextIssueSchema)
10796
10959
  });
10797
10960
  var SandboxConfigSchema = import_v48.z.object({
10798
- enabled: import_v48.z.boolean().optional(),
10961
+ /**
10962
+ * @deprecated Not implemented. Sandbox mode is chosen by the caller
10963
+ * (playground, scenario replay, CLI), never by the agent file.
10964
+ */
10965
+ enabled: import_v48.z.boolean().optional().describe("Not implemented"),
10799
10966
  /** @deprecated Use 'context' instead of 'mockContext' */
10800
10967
  mockContext: AgentContextSchema.optional(),
10801
10968
  context: AgentContextSchema.optional()
@@ -10967,24 +11134,87 @@ var ResponsesBehaviorConfigSchema = import_v49.z.object({
10967
11134
  * @default false
10968
11135
  */
10969
11136
  allowSilent: import_v49.z.boolean().optional(),
11137
+ /**
11138
+ * Strip em dashes, smart quotes and bullet leaders from outbound messages.
11139
+ * @default false
11140
+ */
11141
+ humanize: import_v49.z.boolean().optional(),
10970
11142
  /**
10971
11143
  * Message splitting configuration.
10972
- * Controls whether and how the agent splits responses into multiple messages.
11144
+ * @deprecated Not implemented — accepted but never applied at runtime.
10973
11145
  */
10974
11146
  messageSplitting: import_v49.z.object({
11147
+ enabled: import_v49.z.boolean(),
11148
+ prompt: import_v49.z.string().optional()
11149
+ }).optional().describe("Not implemented"),
11150
+ /**
11151
+ * Response gate configuration.
11152
+ * Automatically detects acknowledgment messages and recommends skipping
11153
+ * responses when a scheduled follow-up already exists.
11154
+ */
11155
+ responseGate: import_v49.z.object({
10975
11156
  /**
10976
- * Whether to allow natural message splitting.
10977
- * When true, the agent may split responses into multiple messages
10978
- * when it improves conversational flow.
11157
+ * Whether the response gate is enabled.
11158
+ * When true, the platform evaluates incoming messages for acknowledgment
11159
+ * patterns and may recommend skipping a response if scheduled follow-ups exist.
11160
+ * @default true (when allowSilent is true)
10979
11161
  */
10980
- enabled: import_v49.z.boolean(),
11162
+ enabled: import_v49.z.boolean().optional(),
10981
11163
  /**
10982
- * Custom prompt to override the default message splitting guidance.
10983
- * If not provided, uses sensible defaults for when to split vs. keep together.
11164
+ * Whether to hard-block sends when gate recommends skip.
11165
+ * When true, system:message:send is blocked unless overrideGate is provided.
11166
+ * When false (default), returns an error encouraging skip but allows override.
11167
+ * @default false
10984
11168
  */
10985
- prompt: import_v49.z.string().optional()
11169
+ strictMode: import_v49.z.boolean().optional(),
11170
+ /**
11171
+ * Minimum confidence threshold for acknowledgment detection.
11172
+ * Only recommend skip when confidence meets this threshold.
11173
+ * @default 0.7
11174
+ */
11175
+ minAckConfidence: import_v49.z.number().min(0).max(1).optional()
10986
11176
  }).optional()
10987
11177
  });
11178
+ var ConversationPolicyLevelSchema = import_v49.z.enum(["off", "prompt", "reject"]);
11179
+ var ConversationBehaviorConfigSchema = import_v49.z.object({
11180
+ /** Do not re-ask something the customer already answered. */
11181
+ repeatQuestions: ConversationPolicyLevelSchema.optional(),
11182
+ /** Do not send skill example wording verbatim. */
11183
+ exampleCopy: ConversationPolicyLevelSchema.optional(),
11184
+ /**
11185
+ * A named delay or a promised check-in should produce a scheduled send.
11186
+ * `prompt` reprompts once and accepts the turn either way; `reject` fails
11187
+ * the turn when no scheduled send follows.
11188
+ */
11189
+ namedDelayFollowUp: ConversationPolicyLevelSchema.optional(),
11190
+ /**
11191
+ * Keep the turn to one message. Prompt-only — the hard count comes from
11192
+ * `behavior.responses.maxImmediate`.
11193
+ */
11194
+ oneMessagePerTurn: ConversationPolicyLevelSchema.optional(),
11195
+ /**
11196
+ * Skip rather than send filler. Prompt-only, and only stated when
11197
+ * `behavior.responses.allowSilent` is set.
11198
+ */
11199
+ fillerBan: ConversationPolicyLevelSchema.optional(),
11200
+ /**
11201
+ * A bare acknowledgement does not cancel a pending follow-up. Prompt-only,
11202
+ * and only stated when the thread has pending scheduled messages.
11203
+ */
11204
+ cancelPendingOnAck: ConversationPolicyLevelSchema.optional(),
11205
+ /**
11206
+ * Ask one thing at a time. `reject` blocks a send that asks more than one
11207
+ * question. Defaults to `off` at runtime: an agent that collects several
11208
+ * answers in one message is a legitimate design.
11209
+ */
11210
+ oneQuestionPerMessage: ConversationPolicyLevelSchema.optional(),
11211
+ /**
11212
+ * Fallback when every send this turn was rejected by a policy gate.
11213
+ * `send` releases the last rejected message, `skip` records a skip.
11214
+ * @default "send"
11215
+ */
11216
+ onPolicyRejectExhausted: import_v49.z.enum(["send", "skip"]).optional()
11217
+ });
10988
11218
  var SchedulingDelaySchema = import_v49.z.object({
10989
11219
  /** Time unit amount */
10990
11220
  amount: import_v49.z.number(),
@@ -11032,15 +11262,53 @@ var BehaviorConfigV3Schema = import_v49.z.object({
11032
11262
  * Scheduling behavior - controls when the agent schedules follow-up messages.
11033
11263
  * Patterns define triggers like "user indicates they'll return later".
11034
11264
  */
11035
- scheduling: SchedulingBehaviorConfigSchema.optional()
11265
+ scheduling: SchedulingBehaviorConfigSchema.optional(),
11266
+ /**
11267
+ * Conversation policy - which conversational rules this agent wants, and
11268
+ * whether each is stated in the prompt or enforced at runtime.
11269
+ */
11270
+ conversation: ConversationBehaviorConfigSchema.optional(),
11271
+ /**
11272
+ * Mention a long gap since the last message when one has passed.
11273
+ * @default false
11274
+ */
11275
+ acknowledgeTimeGaps: import_v49.z.boolean().optional(),
11276
+ /**
11277
+ * Gap that counts as long for `acknowledgeTimeGaps`, e.g. "3 days".
11278
+ */
11279
+ timeGapThreshold: import_v49.z.string().optional()
11280
+ });
11281
+ var AgentSettingV3Schema = import_v49.z.object({
11282
+ /** Setting key, resolved against the install's AgentConfig and env. */
11283
+ key: import_v49.z.string().min(1),
11284
+ /** Human label shown to the model and in the console. */
11285
+ label: import_v49.z.string().optional(),
11286
+ /**
11287
+ * Prompt guidance for this setting. `{{value}}` interpolates the install
11288
+ * value and `{{workplace}}` the workplace name.
11289
+ */
11290
+ guidance: import_v49.z.string().optional(),
11291
+ /**
11292
+ * Marks a setting as supplying a platform value rather than prose.
11293
+ * `persona_name` overrides the agent's persona name for this install.
11294
+ */
11295
+ role: import_v49.z.enum(["persona_name"]).optional()
11036
11296
  });
11037
11297
  var PromptsConfigV3Schema = import_v49.z.object({
11038
11298
  /** Main system prompt with workflow instructions */
11039
11299
  system: import_v49.z.string().optional(),
11040
- /** Injected during second pass when skills were loaded but tools not used */
11041
- recovery: import_v49.z.string().optional(),
11042
- /** Injected during follow-up passes when context needs updating */
11043
- followUp: import_v49.z.string().optional(),
11300
+ /**
11301
+ * Injected during second pass when skills were loaded but tools not used.
11302
+ * @deprecated Ignored on the durable turn path; only the legacy stream path
11303
+ * reads it. Put the guidance in `system` instead.
11304
+ */
11305
+ recovery: import_v49.z.string().optional().describe("Not implemented"),
11306
+ /**
11307
+ * Injected during follow-up passes when context needs updating.
11308
+ * @deprecated Ignored on the durable turn path; only the legacy stream path
11309
+ * reads it. Put the guidance in `system` instead.
11310
+ */
11311
+ followUp: import_v49.z.string().optional().describe("Not implemented"),
11044
11312
  /** Thread list title generation (system + user template with {{var}} placeholders) */
11045
11313
  titleEnrichment: import_v49.z.object({
11046
11314
  system: import_v49.z.string().optional(),
@@ -11060,18 +11328,23 @@ var AgentYAMLV3Schema = import_v49.z.object({
11060
11328
  // Tools - Always-available tools before any skill loads
11061
11329
  // Examples: system:settings:business_information:get
11062
11330
  tools: import_v49.z.array(AgentToolRefSchema).optional(),
11331
+ /**
11332
+ * Settings - Install-scoped values the agent asks for by key.
11333
+ * Rendered into the prompt with the label and guidance declared here.
11334
+ */
11335
+ settings: import_v49.z.array(AgentSettingV3Schema).optional(),
11063
11336
  /**
11064
11337
  * Events - When the agent activates
11065
11338
  * @deprecated Not yet implemented - this is a planned feature for event-driven agents.
11066
11339
  * Fields are accepted but not used at runtime.
11067
11340
  */
11068
- events: EventsConfigSchema.optional(),
11341
+ events: EventsConfigSchema.optional().describe("Not implemented"),
11069
11342
  /**
11070
11343
  * Memory - How the agent remembers
11071
11344
  * @deprecated Not yet implemented - this is a planned feature.
11072
11345
  * Fields are accepted but not used at runtime.
11073
11346
  */
11074
- memory: MemoryConfigV3Schema.optional(),
11347
+ memory: MemoryConfigV3Schema.optional().describe("Not implemented"),
11075
11348
  // Policies - Business rules for the agent
11076
11349
  policies: PoliciesConfigV3Schema.optional(),
11077
11350
  // Runtime - Execution configuration (model + personaModel)
@@ -11125,13 +11398,15 @@ Get Options:
11125
11398
  --json Output as JSON
11126
11399
 
11127
11400
  Deploy Options:
11128
- --file, -f Path to agent definition file (.agent.yml or .agent.json)
11401
+ --file, -f Path to agent definition file (.agent.yml, .agent.json, or .agent-pack.yaml)
11129
11402
  --workplace, -w Workplace subdomain (required)
11130
11403
  --draft Deploy as draft (not published)
11404
+ --upload Force multipart upload (for large files >500KB)
11131
11405
  --json Output as JSON
11132
11406
 
11133
- Supports both legacy (multi-stage) and v3 (skills-based) agent formats.
11134
- V3 agents use skills, events, and memory instead of stages.
11407
+ Supports v3 (skills-based) agents and agent-pack bundles.
11408
+ Agent-pack files (.agent-pack.yaml) include embedded skills, evaluators, and scenarios.
11409
+ Files larger than 500KB automatically use multipart upload.
11135
11410
 
11136
11411
  Publish Options:
11137
11412
  --workplace, -w Workplace subdomain (required)
@@ -11447,12 +11722,14 @@ async function handleUpdate2(args2) {
11447
11722
  console.log('Note: "update" is deprecated. Use "deploy" instead.');
11448
11723
  await handleDeploy(args2);
11449
11724
  }
11725
+ var MULTIPART_THRESHOLD = 500 * 1024;
11450
11726
  async function handleDeploy(args2) {
11451
11727
  const { flags } = parseArgs(args2);
11452
11728
  const filePath = flags.file || flags.f;
11453
11729
  const workplace = flags.workplace || flags.w;
11454
11730
  const isDraft = Boolean(flags.draft);
11455
11731
  const jsonOutput = Boolean(flags.json);
11732
+ const forceUpload = Boolean(flags.upload);
11456
11733
  if (!filePath) {
11457
11734
  console.error("Error: --file (-f) is required");
11458
11735
  console.error("Usage: skedyul agents deploy --file ./agent.yml --workplace gym-demo");
@@ -11466,11 +11743,25 @@ async function handleDeploy(args2) {
11466
11743
  let agent;
11467
11744
  let content;
11468
11745
  let isV3;
11746
+ let isAgentPack = false;
11469
11747
  try {
11470
- const result = await loadAgentFile(filePath);
11471
- agent = result.agent;
11472
- content = result.content;
11473
- isV3 = result.isV3;
11748
+ const absolutePath = path22.resolve(filePath);
11749
+ content = fs22.readFileSync(absolutePath, "utf-8");
11750
+ const rawContent = (0, import_yaml2.parse)(content);
11751
+ isAgentPack = rawContent?.kind === "skedyul.agent-pack";
11752
+ if (isAgentPack) {
11753
+ const agentObj = rawContent.agent;
11754
+ if (!agentObj) {
11755
+ throw new Error('Agent-pack missing "agent" field');
11756
+ }
11757
+ agent = agentObj;
11758
+ isV3 = true;
11759
+ } else {
11760
+ const result = await loadAgentFile(filePath);
11761
+ agent = result.agent;
11762
+ content = result.content;
11763
+ isV3 = result.isV3;
11764
+ }
11474
11765
  } catch (error) {
11475
11766
  if (jsonOutput) {
11476
11767
  console.log(JSON.stringify({ error: error instanceof Error ? error.message : String(error) }));
@@ -11491,37 +11782,46 @@ async function handleDeploy(args2) {
11491
11782
  }
11492
11783
  process.exit(1);
11493
11784
  }
11785
+ const fileSize = Buffer.byteLength(content, "utf8");
11786
+ const useMultipart = forceUpload || fileSize > MULTIPART_THRESHOLD || isAgentPack;
11494
11787
  if (!jsonOutput) {
11495
11788
  let agentType;
11496
- if (isV3) {
11789
+ if (isAgentPack) {
11790
+ agentType = "agent-pack (bundled)";
11791
+ } else if (isV3) {
11497
11792
  agentType = "v3 (skills-based)";
11498
11793
  } else {
11499
11794
  agentType = isMultiStageAgent(agent) ? "multi-stage" : "single-stage";
11500
11795
  }
11501
11796
  console.log("");
11502
11797
  console.log(`Deploying ${agentType} agent "${agent.name}" to ${workplace}${isDraft ? " (draft)" : ""}`);
11798
+ if (useMultipart) {
11799
+ console.log(` Using multipart upload (${(fileSize / 1024).toFixed(1)} KB)`);
11800
+ }
11503
11801
  console.log("");
11504
11802
  }
11505
11803
  try {
11506
- const response = await fetch(`${serverUrl}/api/cli/agents`, {
11507
- method: "POST",
11508
- headers: {
11509
- "Content-Type": "application/json",
11510
- Authorization: `Bearer ${token}`
11511
- },
11512
- body: JSON.stringify({
11804
+ let result;
11805
+ if (useMultipart) {
11806
+ result = await deployAgentMultipart({
11807
+ serverUrl,
11808
+ token,
11513
11809
  workplaceId: workplaceToken.workplaceId,
11514
11810
  subdomain: workplaceToken.workplaceSubdomain,
11515
- action: "deploy",
11516
- yamlContent: content,
11811
+ filePath,
11812
+ content,
11517
11813
  publish: !isDraft
11518
- })
11519
- });
11520
- if (!response.ok) {
11521
- const errorData = await response.json().catch(() => ({}));
11522
- throw new Error(errorData.error || `Request failed: ${response.statusText}`);
11814
+ });
11815
+ } else {
11816
+ result = await deployAgentJson({
11817
+ serverUrl,
11818
+ token,
11819
+ workplaceId: workplaceToken.workplaceId,
11820
+ subdomain: workplaceToken.workplaceSubdomain,
11821
+ content,
11822
+ publish: !isDraft
11823
+ });
11523
11824
  }
11524
- const result = await response.json();
11525
11825
  if (!result.success) {
11526
11826
  throw new Error(result.error || "Failed to deploy agent");
11527
11827
  }
@@ -11549,6 +11849,12 @@ async function handleDeploy(args2) {
11549
11849
  console.log(` Tools: ${result.agent.tools.length} bound`);
11550
11850
  }
11551
11851
  }
11852
+ if (result.deployedSkills?.length) {
11853
+ console.log(` Skills: ${result.deployedSkills.length} deployed`);
11854
+ for (const skill of result.deployedSkills) {
11855
+ console.log(` - ${skill.handle} v${skill.version}`);
11856
+ }
11857
+ }
11552
11858
  console.log("");
11553
11859
  } catch (error) {
11554
11860
  if (jsonOutput) {
@@ -11559,6 +11865,50 @@ async function handleDeploy(args2) {
11559
11865
  process.exit(1);
11560
11866
  }
11561
11867
  }
11868
+ async function deployAgentJson(params) {
11869
+ const response = await fetch(`${params.serverUrl}/api/cli/agents`, {
11870
+ method: "POST",
11871
+ headers: {
11872
+ "Content-Type": "application/json",
11873
+ Authorization: `Bearer ${params.token}`
11874
+ },
11875
+ body: JSON.stringify({
11876
+ workplaceId: params.workplaceId,
11877
+ subdomain: params.subdomain,
11878
+ action: "deploy",
11879
+ yamlContent: params.content,
11880
+ publish: params.publish
11881
+ })
11882
+ });
11883
+ if (!response.ok) {
11884
+ const errorData = await response.json().catch(() => ({}));
11885
+ throw new Error(errorData.error || `Request failed: ${response.statusText}`);
11886
+ }
11887
+ return response.json();
11888
+ }
11889
+ async function deployAgentMultipart(params) {
11890
+ const formData = new FormData();
11891
+ const blob = new Blob([params.content], { type: "text/yaml" });
11892
+ const fileName = path22.basename(params.filePath);
11893
+ formData.append("file", blob, fileName);
11894
+ formData.append("metadata", JSON.stringify({
11895
+ workplaceId: params.workplaceId,
11896
+ subdomain: params.subdomain,
11897
+ publish: params.publish
11898
+ }));
11899
+ const response = await fetch(`${params.serverUrl}/api/cli/agents/upload`, {
11900
+ method: "POST",
11901
+ headers: {
11902
+ Authorization: `Bearer ${params.token}`
11903
+ },
11904
+ body: formData
11905
+ });
11906
+ if (!response.ok) {
11907
+ const errorData = await response.json().catch(() => ({}));
11908
+ throw new Error(errorData.error || `Request failed: ${response.statusText}`);
11909
+ }
11910
+ return response.json();
11911
+ }
11562
11912
  async function handlePublish(args2) {
11563
11913
  const { flags, positional } = parseArgs(args2);
11564
11914
  const handle = positional[0];
@@ -13211,8 +13561,11 @@ Deploy Options:
13211
13561
  --workplace, -w Workplace subdomain (required)
13212
13562
  --draft Deploy as draft (not published)
13213
13563
  --label Version label (e.g., "experiment-shorter-prompts")
13564
+ --upload Force multipart upload (for large files >500KB)
13214
13565
  --json Output as JSON
13215
13566
 
13567
+ Note: Files larger than 500KB automatically use multipart upload.
13568
+
13216
13569
  Publish Options:
13217
13570
  --workplace, -w Workplace subdomain (required)
13218
13571
  --version, -v Version number to publish (required)
@@ -13419,6 +13772,7 @@ async function handleGet3(args2) {
13419
13772
  process.exit(1);
13420
13773
  }
13421
13774
  }
13775
+ var MULTIPART_THRESHOLD2 = 500 * 1024;
13422
13776
  async function handleDeploy2(args2) {
13423
13777
  const { flags } = parseArgs(args2);
13424
13778
  const filePath = flags.file || flags.f;
@@ -13426,6 +13780,7 @@ async function handleDeploy2(args2) {
13426
13780
  const isDraft = Boolean(flags.draft);
13427
13781
  const versionLabel = flags.label;
13428
13782
  const jsonOutput = Boolean(flags.json);
13783
+ const forceUpload = Boolean(flags.upload);
13429
13784
  if (!filePath) {
13430
13785
  console.error("Error: --file (-f) is required");
13431
13786
  console.error("Usage: skedyul skills deploy --file ./skill.yml --workplace crux");
@@ -13462,36 +13817,38 @@ async function handleDeploy2(args2) {
13462
13817
  }
13463
13818
  process.exit(1);
13464
13819
  }
13820
+ const fileSize = Buffer.byteLength(content, "utf8");
13821
+ const useMultipart = forceUpload || fileSize > MULTIPART_THRESHOLD2;
13465
13822
  if (!jsonOutput) {
13466
13823
  console.log("");
13467
13824
  console.log(`Deploying skill "${skill.name}" to ${workplace}${isDraft ? " (draft)" : ""}`);
13825
+ if (useMultipart) {
13826
+ console.log(` Using multipart upload (${(fileSize / 1024).toFixed(1)} KB)`);
13827
+ }
13468
13828
  console.log("");
13469
13829
  }
13470
13830
  try {
13471
- const response = await fetch(`${serverUrl}/api/cli/skills`, {
13472
- method: "POST",
13473
- headers: {
13474
- "Content-Type": "application/json",
13475
- Authorization: `Bearer ${token}`
13476
- },
13477
- body: JSON.stringify({
13478
- action: "deploy",
13831
+ let result;
13832
+ if (useMultipart) {
13833
+ result = await deploySkillMultipart({
13834
+ serverUrl,
13835
+ token,
13479
13836
  workplaceId: workplaceToken.workplaceId,
13480
- yamlContent: content,
13837
+ filePath,
13838
+ content,
13481
13839
  publish: !isDraft,
13482
13840
  versionLabel
13483
- })
13484
- });
13485
- if (!response.ok) {
13486
- const errorData = await response.json().catch(() => ({}));
13487
- if (errorData.details) {
13488
- const detailsStr = errorData.details.map((d) => ` - ${d.path}: ${d.message}`).join("\n");
13489
- throw new Error(`${errorData.error}
13490
- ${detailsStr}`);
13491
- }
13492
- throw new Error(errorData.error || `Request failed: ${response.statusText}`);
13841
+ });
13842
+ } else {
13843
+ result = await deploySkillJson({
13844
+ serverUrl,
13845
+ token,
13846
+ workplaceId: workplaceToken.workplaceId,
13847
+ content,
13848
+ publish: !isDraft,
13849
+ versionLabel
13850
+ });
13493
13851
  }
13494
- const result = await response.json();
13495
13852
  if (!result.success) {
13496
13853
  throw new Error(result.error || "Failed to deploy skill");
13497
13854
  }
@@ -13518,6 +13875,60 @@ ${detailsStr}`);
13518
13875
  process.exit(1);
13519
13876
  }
13520
13877
  }
13878
+ async function deploySkillJson(params) {
13879
+ const response = await fetch(`${params.serverUrl}/api/cli/skills`, {
13880
+ method: "POST",
13881
+ headers: {
13882
+ "Content-Type": "application/json",
13883
+ Authorization: `Bearer ${params.token}`
13884
+ },
13885
+ body: JSON.stringify({
13886
+ action: "deploy",
13887
+ workplaceId: params.workplaceId,
13888
+ yamlContent: params.content,
13889
+ publish: params.publish,
13890
+ versionLabel: params.versionLabel
13891
+ })
13892
+ });
13893
+ if (!response.ok) {
13894
+ const errorData = await response.json().catch(() => ({}));
13895
+ if (errorData.details) {
13896
+ const detailsStr = errorData.details.map((d) => ` - ${d.path}: ${d.message}`).join("\n");
13897
+ throw new Error(`${errorData.error}
13898
+ ${detailsStr}`);
13899
+ }
13900
+ throw new Error(errorData.error || `Request failed: ${response.statusText}`);
13901
+ }
13902
+ return response.json();
13903
+ }
13904
+ async function deploySkillMultipart(params) {
13905
+ const formData = new FormData();
13906
+ const blob = new Blob([params.content], { type: "text/yaml" });
13907
+ const fileName = path24.basename(params.filePath);
13908
+ formData.append("file", blob, fileName);
13909
+ formData.append("metadata", JSON.stringify({
13910
+ workplaceId: params.workplaceId,
13911
+ publish: params.publish,
13912
+ versionLabel: params.versionLabel
13913
+ }));
13914
+ const response = await fetch(`${params.serverUrl}/api/cli/skills/upload`, {
13915
+ method: "POST",
13916
+ headers: {
13917
+ Authorization: `Bearer ${params.token}`
13918
+ },
13919
+ body: formData
13920
+ });
13921
+ if (!response.ok) {
13922
+ const errorData = await response.json().catch(() => ({}));
13923
+ if (errorData.details) {
13924
+ const detailsStr = errorData.details.map((d) => ` - ${d.path}: ${d.message}`).join("\n");
13925
+ throw new Error(`${errorData.error}
13926
+ ${detailsStr}`);
13927
+ }
13928
+ throw new Error(errorData.error || `Request failed: ${response.statusText}`);
13929
+ }
13930
+ return response.json();
13931
+ }
13521
13932
  async function handlePublish2(args2) {
13522
13933
  const { flags, positional } = parseArgs(args2);
13523
13934
  const handle = positional[0];