@polos/sdk 0.2.1 → 0.2.3

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/index.cjs CHANGED
@@ -748,6 +748,11 @@ var OrchestratorClient = class {
748
748
  if (request.waitForSubworkflow !== void 0)
749
749
  body["wait_for_subworkflow"] = request.waitForSubworkflow;
750
750
  if (request.otelTraceparent !== void 0) body["otel_traceparent"] = request.otelTraceparent;
751
+ if (request.channelContext !== void 0)
752
+ body["channel_context"] = {
753
+ channel_id: request.channelContext.channelId,
754
+ source: request.channelContext.source
755
+ };
751
756
  return this.request("POST", "/api/v1/workflows/batch_run", {
752
757
  body
753
758
  });
@@ -3381,6 +3386,187 @@ function defineAgent(config) {
3381
3386
  });
3382
3387
  return agentWorkflow;
3383
3388
  }
3389
+
3390
+ // src/channels/slack.ts
3391
+ var SlackChannel = class {
3392
+ id = "slack";
3393
+ outputMode = "per_step";
3394
+ config;
3395
+ constructor(config) {
3396
+ if (!config.botToken.startsWith("xoxb-")) {
3397
+ throw new Error(
3398
+ `Invalid Slack bot token: must start with "xoxb-". Use the Bot User OAuth Token from your Slack app's OAuth & Permissions page.`
3399
+ );
3400
+ }
3401
+ this.config = config;
3402
+ }
3403
+ async notify(notification) {
3404
+ const overrides = notification.channelOverrides;
3405
+ const channel = overrides?.["channel"] ?? this.config.defaultChannel;
3406
+ const threadTs = overrides?.["thread_ts"] ?? overrides?.["threadTs"];
3407
+ const blocks = this.buildBlocks(notification);
3408
+ const text = notification.title ?? "Agent needs your input";
3409
+ const messageTs = await this.postMessage(channel, threadTs, text, blocks);
3410
+ return {
3411
+ slack_channel: channel,
3412
+ slack_message_ts: messageTs,
3413
+ slack_blocks: blocks
3414
+ };
3415
+ }
3416
+ async sendOutput(context2, event) {
3417
+ const channel = context2.source["channel"];
3418
+ const threadTs = context2.source["threadTs"];
3419
+ if (!channel) return;
3420
+ const text = this.formatOutputEvent(event);
3421
+ if (!text) return;
3422
+ await this.postMessage(channel, threadTs, text);
3423
+ }
3424
+ async postMessage(channel, threadTs, text, blocks) {
3425
+ const body = { channel, text };
3426
+ if (threadTs) body["thread_ts"] = threadTs;
3427
+ if (blocks) body["blocks"] = blocks;
3428
+ const response = await fetch("https://slack.com/api/chat.postMessage", {
3429
+ method: "POST",
3430
+ headers: {
3431
+ Authorization: `Bearer ${this.config.botToken}`,
3432
+ "Content-Type": "application/json"
3433
+ },
3434
+ body: JSON.stringify(body)
3435
+ });
3436
+ const data = await response.json();
3437
+ if (!data.ok) {
3438
+ throw new Error(`Slack API error: ${data.error ?? "unknown"}`);
3439
+ }
3440
+ return data.ts ?? "";
3441
+ }
3442
+ formatOutputEvent(event) {
3443
+ const eventType = event.eventType;
3444
+ if (eventType === "workflow_finish" || eventType === "agent_finish") {
3445
+ const metadata = event.data["_metadata"];
3446
+ const rawResult = event.data["result"];
3447
+ const error = event.data["error"];
3448
+ const workflowId = metadata?.["workflow_id"];
3449
+ if (error) {
3450
+ return `\u274C *${workflowId ?? "Workflow"} failed:* ${error}`;
3451
+ }
3452
+ const result = typeof rawResult === "object" && rawResult !== null && "result" in rawResult ? rawResult["result"] : rawResult;
3453
+ const resultStr = typeof result === "string" ? result : JSON.stringify(result, null, 2);
3454
+ if (resultStr) {
3455
+ return resultStr;
3456
+ }
3457
+ return `\u2705 *${workflowId ?? "Workflow"} finished*`;
3458
+ }
3459
+ if (eventType === "tool_call") {
3460
+ const toolCall = event.data["tool_call"];
3461
+ if (toolCall) {
3462
+ const fn = toolCall["function"];
3463
+ const name = fn?.["name"];
3464
+ if (name) {
3465
+ return `\u{1F527} Calling tool: \`${name}\``;
3466
+ }
3467
+ }
3468
+ return null;
3469
+ }
3470
+ if (eventType === "step_finish") {
3471
+ const stepKey = event.data["step_key"];
3472
+ const error = event.data["error"];
3473
+ if (error) {
3474
+ return `\u26A0\uFE0F Step \`${stepKey ?? "unknown"}\` failed: ${error}`;
3475
+ }
3476
+ return null;
3477
+ }
3478
+ return null;
3479
+ }
3480
+ buildBlocks(n) {
3481
+ const blocks = [];
3482
+ blocks.push({
3483
+ type: "header",
3484
+ text: { type: "plain_text", text: n.title ?? "Agent needs your input" }
3485
+ });
3486
+ if (n.description) {
3487
+ blocks.push({
3488
+ type: "section",
3489
+ text: { type: "mrkdwn", text: n.description }
3490
+ });
3491
+ }
3492
+ if (n.source || n.tool) {
3493
+ const parts = [];
3494
+ if (n.source) parts.push(`*Source:* ${n.source}`);
3495
+ if (n.tool) parts.push(`*Tool:* \`${n.tool}\``);
3496
+ blocks.push({
3497
+ type: "context",
3498
+ elements: [{ type: "mrkdwn", text: parts.join(" | ") }]
3499
+ });
3500
+ }
3501
+ if (n.context && Object.keys(n.context).length > 0) {
3502
+ const contextText = JSON.stringify(n.context, null, 2);
3503
+ blocks.push({
3504
+ type: "section",
3505
+ text: { type: "mrkdwn", text: "```" + contextText + "```" }
3506
+ });
3507
+ }
3508
+ if (n.expiresAt) {
3509
+ blocks.push({
3510
+ type: "context",
3511
+ elements: [{ type: "mrkdwn", text: `Expires: ${n.expiresAt}` }]
3512
+ });
3513
+ }
3514
+ if (this.isSimpleApproval(n)) {
3515
+ const approveValue = JSON.stringify({
3516
+ executionId: n.executionId,
3517
+ stepKey: n.stepKey,
3518
+ approved: true
3519
+ });
3520
+ const rejectValue = JSON.stringify({
3521
+ executionId: n.executionId,
3522
+ stepKey: n.stepKey,
3523
+ approved: false
3524
+ });
3525
+ blocks.push({
3526
+ type: "actions",
3527
+ elements: [
3528
+ {
3529
+ type: "button",
3530
+ action_id: "polos_approve",
3531
+ text: { type: "plain_text", text: "Approve" },
3532
+ style: "primary",
3533
+ value: approveValue
3534
+ },
3535
+ {
3536
+ type: "button",
3537
+ action_id: "polos_reject",
3538
+ text: { type: "plain_text", text: "Reject" },
3539
+ style: "danger",
3540
+ value: rejectValue
3541
+ },
3542
+ {
3543
+ type: "button",
3544
+ text: { type: "plain_text", text: "View Details" },
3545
+ url: n.approvalUrl
3546
+ }
3547
+ ]
3548
+ });
3549
+ } else {
3550
+ blocks.push({
3551
+ type: "actions",
3552
+ elements: [
3553
+ {
3554
+ type: "button",
3555
+ text: { type: "plain_text", text: "Respond" },
3556
+ url: n.approvalUrl,
3557
+ style: "primary"
3558
+ }
3559
+ ]
3560
+ });
3561
+ }
3562
+ return blocks;
3563
+ }
3564
+ isSimpleApproval(n) {
3565
+ const fields = n.formFields;
3566
+ if (!fields || fields.length === 0) return false;
3567
+ return fields.some((f) => f["key"] === "approved" && f["type"] === "boolean");
3568
+ }
3569
+ };
3384
3570
  var logger4 = createLogger({ name: "worker-server" });
3385
3571
  var WorkerServer = class {
3386
3572
  app;
@@ -4234,7 +4420,8 @@ function createOrchestratorStepHelper(orchestratorClient, cachedSteps, execCtx,
4234
4420
  otelTraceparent: getCurrentTraceparent(),
4235
4421
  waitForSubworkflow: options?.waitForSubworkflow ?? false,
4236
4422
  initialState: options?.initialState,
4237
- runTimeoutSeconds: options?.runTimeoutSeconds
4423
+ runTimeoutSeconds: options?.runTimeoutSeconds,
4424
+ channelContext
4238
4425
  });
4239
4426
  if (options?.waitForSubworkflow) {
4240
4427
  return [void 0, false];
@@ -4422,7 +4609,8 @@ function createOrchestratorStepHelper(orchestratorClient, cachedSteps, execCtx,
4422
4609
  sessionId: execCtx.sessionId,
4423
4610
  userId: execCtx.userId,
4424
4611
  waitForSubworkflow: false,
4425
- otelTraceparent: getCurrentTraceparent()
4612
+ otelTraceparent: getCurrentTraceparent(),
4613
+ channelContext
4426
4614
  });
4427
4615
  const handleData = response.executions.map((exec, i) => {
4428
4616
  const item = items[i];
@@ -4498,7 +4686,8 @@ function createOrchestratorStepHelper(orchestratorClient, cachedSteps, execCtx,
4498
4686
  sessionId: execCtx.sessionId,
4499
4687
  userId: execCtx.userId,
4500
4688
  waitForSubworkflow: true,
4501
- otelTraceparent: getCurrentTraceparent()
4689
+ otelTraceparent: getCurrentTraceparent(),
4690
+ channelContext
4502
4691
  });
4503
4692
  const workflowIds = items.map((item) => typeof item.workflow === "string" ? item.workflow : item.workflow.id).join(", ");
4504
4693
  throw new WaitError(`Waiting for sub-workflows [${workflowIds}] to complete`, {
@@ -4694,7 +4883,7 @@ function createOrchestratorStepHelper(orchestratorClient, cachedSteps, execCtx,
4694
4883
  if (channelContext !== void 0) {
4695
4884
  notification.channelContext = channelContext;
4696
4885
  }
4697
- await Promise.allSettled(
4886
+ const notifyResults = await Promise.allSettled(
4698
4887
  channels.map(async (ch) => {
4699
4888
  try {
4700
4889
  let overrides = notifyConfig?.[ch.id];
@@ -4702,12 +4891,35 @@ function createOrchestratorStepHelper(orchestratorClient, cachedSteps, execCtx,
4702
4891
  overrides = { ...channelContext.source, ...overrides };
4703
4892
  }
4704
4893
  const n = overrides !== void 0 ? { ...notification, channelOverrides: overrides } : notification;
4705
- await ch.notify(n);
4894
+ const meta = await ch.notify(n);
4895
+ return meta ? { channelId: ch.id, ...meta } : void 0;
4706
4896
  } catch (err) {
4707
4897
  logger6.warn(`Channel ${ch.id} notification failed`, { error: String(err) });
4898
+ return void 0;
4708
4899
  }
4709
4900
  })
4710
4901
  );
4902
+ const metaEntries = [];
4903
+ for (const r of notifyResults) {
4904
+ if (r.status === "fulfilled" && r.value != null) {
4905
+ metaEntries.push(r.value);
4906
+ }
4907
+ }
4908
+ if (metaEntries.length > 0) {
4909
+ orchestratorClient.publishEvent({
4910
+ topic,
4911
+ events: [
4912
+ {
4913
+ eventType: `notification_meta_${key}`,
4914
+ data: { channels: metaEntries }
4915
+ }
4916
+ ],
4917
+ executionId: execCtx.executionId,
4918
+ rootExecutionId: execCtx.rootExecutionId
4919
+ }).catch((err) => {
4920
+ logger6.warn("Failed to publish notification metadata", { error: String(err) });
4921
+ });
4922
+ }
4711
4923
  }
4712
4924
  throw new WaitError(`Waiting for resume event: ${topic}`, { topic });
4713
4925
  },
@@ -5348,7 +5560,27 @@ function spawnCommand(command, args, options) {
5348
5560
  killed = true;
5349
5561
  proc.kill("SIGKILL");
5350
5562
  }, timeoutMs);
5563
+ let exitGraceTimer = null;
5564
+ let exitCode = null;
5565
+ proc.on("exit", (code) => {
5566
+ exitCode = code;
5567
+ exitGraceTimer = setTimeout(() => {
5568
+ clearTimeout(timer);
5569
+ settle(() => {
5570
+ if (killed) {
5571
+ resolve10({
5572
+ exitCode: 137,
5573
+ stdout,
5574
+ stderr: stderr + "\n[Process killed: timeout exceeded]"
5575
+ });
5576
+ } else {
5577
+ resolve10({ exitCode: exitCode ?? 1, stdout, stderr });
5578
+ }
5579
+ });
5580
+ }, 2e3);
5581
+ });
5351
5582
  proc.on("close", (code) => {
5583
+ if (exitGraceTimer) clearTimeout(exitGraceTimer);
5352
5584
  clearTimeout(timer);
5353
5585
  settle(() => {
5354
5586
  if (killed) {
@@ -5358,11 +5590,12 @@ function spawnCommand(command, args, options) {
5358
5590
  stderr: stderr + "\n[Process killed: timeout exceeded]"
5359
5591
  });
5360
5592
  } else {
5361
- resolve10({ exitCode: code ?? 1, stdout, stderr });
5593
+ resolve10({ exitCode: code ?? exitCode ?? 1, stdout, stderr });
5362
5594
  }
5363
5595
  });
5364
5596
  });
5365
5597
  proc.on("error", (err) => {
5598
+ if (exitGraceTimer) clearTimeout(exitGraceTimer);
5366
5599
  clearTimeout(timer);
5367
5600
  settle(() => {
5368
5601
  reject(err);
@@ -5608,7 +5841,27 @@ function spawnLocal(command, options) {
5608
5841
  killed = true;
5609
5842
  proc.kill("SIGKILL");
5610
5843
  }, timeoutMs);
5844
+ let exitGraceTimer = null;
5845
+ let exitCode = null;
5846
+ proc.on("exit", (code) => {
5847
+ exitCode = code;
5848
+ exitGraceTimer = setTimeout(() => {
5849
+ clearTimeout(timer);
5850
+ settle(() => {
5851
+ if (killed) {
5852
+ resolve10({
5853
+ exitCode: 137,
5854
+ stdout,
5855
+ stderr: stderr + "\n[Process killed: timeout exceeded]"
5856
+ });
5857
+ } else {
5858
+ resolve10({ exitCode: exitCode ?? 1, stdout, stderr });
5859
+ }
5860
+ });
5861
+ }, 2e3);
5862
+ });
5611
5863
  proc.on("close", (code) => {
5864
+ if (exitGraceTimer) clearTimeout(exitGraceTimer);
5612
5865
  clearTimeout(timer);
5613
5866
  settle(() => {
5614
5867
  if (killed) {
@@ -5618,11 +5871,12 @@ function spawnLocal(command, options) {
5618
5871
  stderr: stderr + "\n[Process killed: timeout exceeded]"
5619
5872
  });
5620
5873
  } else {
5621
- resolve10({ exitCode: code ?? 1, stdout, stderr });
5874
+ resolve10({ exitCode: code ?? exitCode ?? 1, stdout, stderr });
5622
5875
  }
5623
5876
  });
5624
5877
  });
5625
5878
  proc.on("error", (err) => {
5879
+ if (exitGraceTimer) clearTimeout(exitGraceTimer);
5626
5880
  clearTimeout(timer);
5627
5881
  settle(() => {
5628
5882
  reject(err);
@@ -6253,10 +6507,24 @@ var Worker = class {
6253
6507
  state = "stopped";
6254
6508
  heartbeatInterval = null;
6255
6509
  activeExecutions = /* @__PURE__ */ new Map();
6510
+ /** Tracks running channel bridges so we don't start duplicates on re-dispatch. */
6511
+ activeChannelBridges = /* @__PURE__ */ new Set();
6256
6512
  signalHandler = null;
6257
6513
  constructor(config) {
6258
6514
  this.config = config;
6259
6515
  this.channels = config.channels ?? [];
6516
+ if (!this.channels.some((ch) => ch.id === "slack")) {
6517
+ const slackBotToken = process.env["SLACK_BOT_TOKEN"];
6518
+ if (slackBotToken) {
6519
+ this.channels.push(
6520
+ new SlackChannel({
6521
+ botToken: slackBotToken,
6522
+ defaultChannel: process.env["SLACK_CHANNEL"] ?? "#general"
6523
+ })
6524
+ );
6525
+ logger9.info("Auto-registered SlackChannel from SLACK_BOT_TOKEN env var");
6526
+ }
6527
+ }
6260
6528
  this.maxConcurrentWorkflows = config.maxConcurrentWorkflows ?? 100;
6261
6529
  this.workerServerUrl = config.workerServerUrl ?? `http://localhost:${String(config.port ?? 8e3)}`;
6262
6530
  this.port = config.port ?? 8e3;
@@ -6790,6 +7058,11 @@ var Worker = class {
6790
7058
  };
6791
7059
  const hasWorkflowChannels = workflow.config.channels !== void 0;
6792
7060
  const resolvedChannels = workflow.config.channels ?? this.channels;
7061
+ if (data.channelContext && !data.parentExecutionId) {
7062
+ const rootExecId = data.rootExecutionId ?? executionId;
7063
+ const rootWfId = data.rootWorkflowId ?? workflowId;
7064
+ this.startChannelBridge(rootExecId, rootWfId, data.channelContext, workflow);
7065
+ }
6793
7066
  result = await executeWorkflow({
6794
7067
  workflow,
6795
7068
  payload: data.payload,
@@ -6801,9 +7074,6 @@ var Worker = class {
6801
7074
  sandboxManager: this.sandboxManager,
6802
7075
  channelContext: hasWorkflowChannels ? void 0 : data.channelContext
6803
7076
  });
6804
- if (data.channelContext) {
6805
- this.startChannelBridge(executionId, workflowId, data.channelContext, workflow);
6806
- }
6807
7077
  await this.handleExecutionResult(executionId, workflowId, context2, result);
6808
7078
  retryableFailure = !result.success && !result.waiting && (result.retryable ?? true);
6809
7079
  } catch (error) {
@@ -6946,15 +7216,20 @@ var Worker = class {
6946
7216
  /**
6947
7217
  * Start a channel bridge that streams execution events back to the originating channel.
6948
7218
  */
6949
- startChannelBridge(executionId, workflowId, channelCtx, workflow) {
7219
+ startChannelBridge(rootExecutionId, rootWorkflowId, channelCtx, workflow) {
7220
+ if (this.activeChannelBridges.has(rootExecutionId)) return;
6950
7221
  const channel = this.channels.find((ch) => ch.id === channelCtx.channelId);
6951
- if (!channel?.sendOutput) return;
7222
+ if (!channel) {
7223
+ logger9.error(
7224
+ `No channel registered for "${channelCtx.channelId}". ` + (channelCtx.channelId === "slack" ? "Set the SLACK_BOT_TOKEN environment variable so the worker can respond to Slack." : `Register a channel with id "${channelCtx.channelId}" on the worker.`),
7225
+ { executionId: rootExecutionId }
7226
+ );
7227
+ return;
7228
+ }
7229
+ if (!channel.sendOutput) return;
6952
7230
  const outputMode = workflow.config["channelOutputMode"] ?? channel.outputMode ?? "per_step";
6953
7231
  if (outputMode === "none") return;
6954
- const execution = this.activeExecutions.get(executionId);
6955
- if (!execution) return;
6956
- const rootExecutionId = execution.abortController.signal.aborted ? executionId : executionId;
6957
- const rootWorkflowId = workflowId;
7232
+ this.activeChannelBridges.add(rootExecutionId);
6958
7233
  void (async () => {
6959
7234
  try {
6960
7235
  const stream = this.orchestratorClient.streamEvents({
@@ -6962,13 +7237,14 @@ var Worker = class {
6962
7237
  workflowRunId: rootExecutionId
6963
7238
  });
6964
7239
  for await (const event of stream) {
6965
- if (execution.abortController.signal.aborted) break;
6966
7240
  if (shouldForwardEvent(event, outputMode)) {
6967
7241
  await channel.sendOutput?.(channelCtx, event);
6968
7242
  }
6969
7243
  }
6970
7244
  } catch (err) {
6971
- logger9.warn("Channel bridge error", { error: String(err), executionId });
7245
+ logger9.warn("Channel bridge error", { error: String(err), executionId: rootExecutionId });
7246
+ } finally {
7247
+ this.activeChannelBridges.delete(rootExecutionId);
6972
7248
  }
6973
7249
  })();
6974
7250
  }
@@ -7673,181 +7949,6 @@ function sandboxTools(config) {
7673
7949
  return tools;
7674
7950
  }
7675
7951
 
7676
- // src/channels/slack.ts
7677
- var SlackChannel = class {
7678
- id = "slack";
7679
- outputMode = "per_step";
7680
- config;
7681
- constructor(config) {
7682
- if (!config.botToken.startsWith("xoxb-")) {
7683
- throw new Error(
7684
- `Invalid Slack bot token: must start with "xoxb-". Use the Bot User OAuth Token from your Slack app's OAuth & Permissions page.`
7685
- );
7686
- }
7687
- this.config = config;
7688
- }
7689
- async notify(notification) {
7690
- const overrides = notification.channelOverrides;
7691
- const channel = overrides?.["channel"] ?? this.config.defaultChannel;
7692
- const threadTs = overrides?.["thread_ts"];
7693
- const blocks = this.buildBlocks(notification);
7694
- const text = notification.title ?? "Agent needs your input";
7695
- await this.postMessage(channel, threadTs, text, blocks);
7696
- }
7697
- async sendOutput(context2, event) {
7698
- const channel = context2.source["channel"];
7699
- const threadTs = context2.source["threadTs"];
7700
- if (!channel) return;
7701
- const text = this.formatOutputEvent(event);
7702
- if (!text) return;
7703
- await this.postMessage(channel, threadTs, text);
7704
- }
7705
- async postMessage(channel, threadTs, text, blocks) {
7706
- const body = { channel, text };
7707
- if (threadTs) body["thread_ts"] = threadTs;
7708
- if (blocks) body["blocks"] = blocks;
7709
- const response = await fetch("https://slack.com/api/chat.postMessage", {
7710
- method: "POST",
7711
- headers: {
7712
- Authorization: `Bearer ${this.config.botToken}`,
7713
- "Content-Type": "application/json"
7714
- },
7715
- body: JSON.stringify(body)
7716
- });
7717
- const data = await response.json();
7718
- if (!data.ok) {
7719
- throw new Error(`Slack API error: ${data.error ?? "unknown"}`);
7720
- }
7721
- }
7722
- formatOutputEvent(event) {
7723
- const eventType = event.eventType;
7724
- if (eventType === "workflow_finish" || eventType === "agent_finish") {
7725
- const metadata = event.data["_metadata"];
7726
- const result = event.data["result"];
7727
- const error = event.data["error"];
7728
- const workflowId = metadata?.["workflow_id"];
7729
- if (error) {
7730
- return `\u274C *${workflowId ?? "Workflow"} failed:* ${error}`;
7731
- }
7732
- const resultStr = typeof result === "string" ? result : JSON.stringify(result, null, 2);
7733
- if (resultStr) {
7734
- return `\u2705 *${workflowId ?? "Workflow"} finished:*
7735
- ${resultStr}`;
7736
- }
7737
- return `\u2705 *${workflowId ?? "Workflow"} finished*`;
7738
- }
7739
- if (eventType === "tool_call") {
7740
- const toolCall = event.data["tool_call"];
7741
- if (toolCall) {
7742
- const fn = toolCall["function"];
7743
- const name = fn?.["name"];
7744
- if (name) {
7745
- return `\u{1F527} Calling tool: \`${name}\``;
7746
- }
7747
- }
7748
- return null;
7749
- }
7750
- if (eventType === "step_finish") {
7751
- const stepKey = event.data["step_key"];
7752
- const error = event.data["error"];
7753
- if (error) {
7754
- return `\u26A0\uFE0F Step \`${stepKey ?? "unknown"}\` failed: ${error}`;
7755
- }
7756
- return null;
7757
- }
7758
- return null;
7759
- }
7760
- buildBlocks(n) {
7761
- const blocks = [];
7762
- blocks.push({
7763
- type: "header",
7764
- text: { type: "plain_text", text: n.title ?? "Agent needs your input" }
7765
- });
7766
- if (n.description) {
7767
- blocks.push({
7768
- type: "section",
7769
- text: { type: "mrkdwn", text: n.description }
7770
- });
7771
- }
7772
- if (n.source || n.tool) {
7773
- const parts = [];
7774
- if (n.source) parts.push(`*Source:* ${n.source}`);
7775
- if (n.tool) parts.push(`*Tool:* \`${n.tool}\``);
7776
- blocks.push({
7777
- type: "context",
7778
- elements: [{ type: "mrkdwn", text: parts.join(" | ") }]
7779
- });
7780
- }
7781
- if (n.context && Object.keys(n.context).length > 0) {
7782
- const contextText = JSON.stringify(n.context, null, 2);
7783
- blocks.push({
7784
- type: "section",
7785
- text: { type: "mrkdwn", text: "```" + contextText + "```" }
7786
- });
7787
- }
7788
- if (n.expiresAt) {
7789
- blocks.push({
7790
- type: "context",
7791
- elements: [{ type: "mrkdwn", text: `Expires: ${n.expiresAt}` }]
7792
- });
7793
- }
7794
- if (this.isSimpleApproval(n)) {
7795
- const approveValue = JSON.stringify({
7796
- executionId: n.executionId,
7797
- stepKey: n.stepKey,
7798
- approved: true
7799
- });
7800
- const rejectValue = JSON.stringify({
7801
- executionId: n.executionId,
7802
- stepKey: n.stepKey,
7803
- approved: false
7804
- });
7805
- blocks.push({
7806
- type: "actions",
7807
- elements: [
7808
- {
7809
- type: "button",
7810
- action_id: "polos_approve",
7811
- text: { type: "plain_text", text: "Approve" },
7812
- style: "primary",
7813
- value: approveValue
7814
- },
7815
- {
7816
- type: "button",
7817
- action_id: "polos_reject",
7818
- text: { type: "plain_text", text: "Reject" },
7819
- style: "danger",
7820
- value: rejectValue
7821
- },
7822
- {
7823
- type: "button",
7824
- text: { type: "plain_text", text: "View Details" },
7825
- url: n.approvalUrl
7826
- }
7827
- ]
7828
- });
7829
- } else {
7830
- blocks.push({
7831
- type: "actions",
7832
- elements: [
7833
- {
7834
- type: "button",
7835
- text: { type: "plain_text", text: "Respond" },
7836
- url: n.approvalUrl,
7837
- style: "primary"
7838
- }
7839
- ]
7840
- });
7841
- }
7842
- return blocks;
7843
- }
7844
- isSimpleApproval(n) {
7845
- const fields = n.formFields;
7846
- if (!fields || fields.length === 0) return false;
7847
- return fields.some((f) => f["key"] === "approved" && f["type"] === "boolean");
7848
- }
7849
- };
7850
-
7851
7952
  exports.AgentRunConfig = AgentRunConfig;
7852
7953
  exports.DockerEnvironment = DockerEnvironment;
7853
7954
  exports.DuplicateWorkflowError = DuplicateWorkflowError;