@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 +294 -193
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +12 -3
- package/dist/index.d.ts +12 -3
- package/dist/index.js +294 -193
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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(
|
|
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
|
|
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
|
-
|
|
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;
|