stageflow 0.35.0 → 0.36.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.
- package/dist/agent/claudeAdapter.js +6 -0
- package/dist/agent/piAdapter.js +9 -0
- package/dist/agent/port.d.ts +2 -0
- package/dist/channels/config.d.ts +11 -0
- package/dist/channels/config.js +95 -0
- package/dist/channels/configWriter.d.ts +40 -0
- package/dist/channels/configWriter.js +148 -0
- package/dist/channels/connectionEdits.d.ts +34 -0
- package/dist/channels/connectionEdits.js +53 -0
- package/dist/channels/errorInfo.d.ts +18 -0
- package/dist/channels/errorInfo.js +71 -0
- package/dist/channels/errors.d.ts +12 -0
- package/dist/channels/errors.js +13 -0
- package/dist/channels/health.d.ts +41 -0
- package/dist/channels/health.js +99 -0
- package/dist/channels/host.d.ts +55 -0
- package/dist/channels/host.js +185 -0
- package/dist/channels/http.d.ts +31 -0
- package/dist/channels/http.js +79 -0
- package/dist/channels/index.d.ts +10 -0
- package/dist/channels/index.js +9 -0
- package/dist/channels/ledger.d.ts +37 -0
- package/dist/channels/ledger.js +95 -0
- package/dist/channels/providers/agentmail.d.ts +26 -0
- package/dist/channels/providers/agentmail.js +81 -0
- package/dist/channels/providers/kit.d.ts +40 -0
- package/dist/channels/providers/kit.js +78 -0
- package/dist/channels/providers/photonImessage.d.ts +45 -0
- package/dist/channels/providers/photonImessage.js +152 -0
- package/dist/channels/providers/photonWhatsapp.d.ts +49 -0
- package/dist/channels/providers/photonWhatsapp.js +146 -0
- package/dist/channels/providers/slack.d.ts +25 -0
- package/dist/channels/providers/slack.js +101 -0
- package/dist/channels/providers/telegram.d.ts +21 -0
- package/dist/channels/providers/telegram.js +100 -0
- package/dist/channels/providers.d.ts +5 -0
- package/dist/channels/providers.js +22 -0
- package/dist/channels/registry.d.ts +54 -0
- package/dist/channels/registry.js +186 -0
- package/dist/channels/secretStore.d.ts +21 -0
- package/dist/channels/secretStore.js +75 -0
- package/dist/channels/secrets.d.ts +15 -0
- package/dist/channels/secrets.js +50 -0
- package/dist/channels/service.d.ts +54 -0
- package/dist/channels/service.js +307 -0
- package/dist/channels/stageChannels.d.ts +45 -0
- package/dist/channels/stageChannels.js +110 -0
- package/dist/channels/types.d.ts +142 -0
- package/dist/channels/types.js +1 -0
- package/dist/cli/doctorCommand.js +3 -1
- package/dist/config/draftPackage.d.ts +1 -0
- package/dist/config/draftPackage.js +4 -0
- package/dist/config/hostConfig.js +1 -0
- package/dist/config/loadPipeline.js +27 -3
- package/dist/config/loadStage.js +15 -0
- package/dist/config/mergePipelineIncludes.d.ts +3 -0
- package/dist/config/mergePipelineIncludes.js +27 -0
- package/dist/config/pipelineStageKeys.js +1 -0
- package/dist/config/validateCatalog.d.ts +4 -1
- package/dist/config/validateCatalog.js +23 -0
- package/dist/errors/codes.d.ts +1 -1
- package/dist/errors/codes.js +1 -0
- package/dist/mcp/catalogTools.js +1 -0
- package/dist/package-meta.d.ts +1 -1
- package/dist/package-meta.js +1 -1
- package/dist/preflight/channelChecks.d.ts +17 -0
- package/dist/preflight/channelChecks.js +106 -0
- package/dist/preflight/pipelinePreflight.d.ts +9 -2
- package/dist/preflight/pipelinePreflight.js +9 -1
- package/dist/runtime/pipelineRunner.js +3 -1
- package/dist/runtime/repositoryMaterialize.d.ts +1 -1
- package/dist/runtime/runManager.js +4 -1
- package/dist/runtime/stageAttemptBootstrap.js +16 -0
- package/dist/runtime/stageProcessLauncher.js +31 -1
- package/dist/server/bootstrap.d.ts +3 -0
- package/dist/server/bootstrap.js +4 -0
- package/dist/server/channelRoutes.d.ts +9 -0
- package/dist/server/channelRoutes.js +88 -0
- package/dist/server/http.js +11 -0
- package/dist/tools/readEmail.d.ts +3 -3
- package/dist/tools/sendMessage.d.ts +21 -0
- package/dist/tools/sendMessage.js +25 -0
- package/dist/types/pipeline.d.ts +4 -1
- package/dist/types/stage.d.ts +13 -0
- package/dist/ui/assets/index-BOhqnPNg.js +260 -0
- package/dist/ui/assets/{index-CIPbbBUi.css → index-Clv4sJpU.css} +1 -1
- package/dist/ui/index.html +2 -2
- package/examples/channels-smoke/README.md +12 -0
- package/examples/channels-smoke/channels-smoke.pipeline.yaml +11 -0
- package/examples/channels-smoke/channels-smoke.task.yaml +3 -0
- package/examples/channels-smoke/send-email.yaml +32 -0
- package/examples/channels-smoke/send-text.yaml +31 -0
- package/package.json +8 -3
- package/dist/ui/assets/index-BoFPkKFg.js +0 -256
|
@@ -13,6 +13,8 @@ import { buildStageEnvironment, isAmbientBlockedEnv, isForeverDeniedSecret, } fr
|
|
|
13
13
|
import { ensureStageCacheDirs } from "./stageCacheEnv.js";
|
|
14
14
|
import { SF_STAGE_WORKER, STAGE_WORKER_EXIT, } from "./stageWorkerProtocol.js";
|
|
15
15
|
import { emailHostFor, emailWorkerEnvironment, stageEmail, validateStageEmailAccounts } from "../email/host.js";
|
|
16
|
+
import { ChannelError } from "../channels/errors.js";
|
|
17
|
+
import { channelFault, channelHostFor, channelsWorkerEnvironment, stageChannels, validateStageChannelConnections } from "../channels/host.js";
|
|
16
18
|
import { EmailError } from "../email/port.js";
|
|
17
19
|
import { overlayStageBindingEnv, } from "./stageRoots.js";
|
|
18
20
|
function defaultStageCliEntry() {
|
|
@@ -189,6 +191,15 @@ export class StageProcessLauncher {
|
|
|
189
191
|
return { type: "failed", reason: error.code };
|
|
190
192
|
throw error;
|
|
191
193
|
}
|
|
194
|
+
try {
|
|
195
|
+
if (input.stage?.channels?.length)
|
|
196
|
+
validateStageChannelConnections(channelHostFor(input.factoryCwd ?? input.operatorCatalog?.cwd ?? input.rootDir), input.stage);
|
|
197
|
+
}
|
|
198
|
+
catch (error) {
|
|
199
|
+
if (error instanceof ChannelError)
|
|
200
|
+
return { type: "failed", reason: error.code };
|
|
201
|
+
throw error;
|
|
202
|
+
}
|
|
192
203
|
input = { ...input, stage: input.stage ? structuredClone(input.stage) : undefined };
|
|
193
204
|
const acquired = await this.waitForCapacity(input.runId);
|
|
194
205
|
if (!acquired) {
|
|
@@ -363,7 +374,7 @@ export class StageProcessLauncher {
|
|
|
363
374
|
}
|
|
364
375
|
const child = this.forkFn(this.cliEntry, args, {
|
|
365
376
|
cwd: input.rootDir,
|
|
366
|
-
env: emailWorkerEnvironment({ ...childEnv, [SF_STAGE_WORKER]: "1" }, emailHostFor(factoryCwd).accounts),
|
|
377
|
+
env: channelsWorkerEnvironment(emailWorkerEnvironment({ ...childEnv, [SF_STAGE_WORKER]: "1" }, emailHostFor(factoryCwd).accounts), factoryCwd),
|
|
367
378
|
stdio: ["pipe", "pipe", "pipe", "ipc"],
|
|
368
379
|
detached: true,
|
|
369
380
|
// Explicit: do not inherit Host execArgv; set heap from cgroup budget.
|
|
@@ -432,6 +443,25 @@ export class StageProcessLauncher {
|
|
|
432
443
|
})();
|
|
433
444
|
return;
|
|
434
445
|
}
|
|
446
|
+
if (!settled && request?.type === "channel.send") {
|
|
447
|
+
void (async () => {
|
|
448
|
+
if (typeof request.requestId !== "string" || request.requestId.length > 100)
|
|
449
|
+
return;
|
|
450
|
+
let response;
|
|
451
|
+
try {
|
|
452
|
+
const stage = input.stage;
|
|
453
|
+
if (!stage || settled)
|
|
454
|
+
throw new ChannelError("CHANNEL_UNAUTHORIZED");
|
|
455
|
+
response = { receipt: await stageChannels(channelHostFor(factoryCwd), stage, input.runId).send(request.input) };
|
|
456
|
+
}
|
|
457
|
+
catch (error) {
|
|
458
|
+
response = { error: channelFault(error) };
|
|
459
|
+
}
|
|
460
|
+
if (child.connected && !settled)
|
|
461
|
+
child.send({ type: "channel.response", requestId: request.requestId, ...response });
|
|
462
|
+
})();
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
435
465
|
if (!isStageWorkerResult(message))
|
|
436
466
|
return;
|
|
437
467
|
finish(resultFromWorkerMessage(message));
|
|
@@ -9,6 +9,7 @@ import type { StageBrowserSupport } from "../browser/browserHost.js";
|
|
|
9
9
|
import { RunManager } from "../runtime/runManager.js";
|
|
10
10
|
import { emailHostFor } from "../email/host.js";
|
|
11
11
|
import { EmailTriggers } from "../email/triggers.js";
|
|
12
|
+
import { type ChannelService } from "../channels/service.js";
|
|
12
13
|
import { type RunChangeBus } from "../runtime/runChangeBus.js";
|
|
13
14
|
import { type McpHttpHandler } from "../mcp/server.js";
|
|
14
15
|
import { type HostConfig } from "../config/hostConfig.js";
|
|
@@ -59,6 +60,8 @@ export type StageflowHostBootstrap = {
|
|
|
59
60
|
stopGithubPollSource: () => void;
|
|
60
61
|
/** Stops the email-trigger IMAP/IDLE listeners started at boot. */
|
|
61
62
|
stopEmailSource: () => void;
|
|
63
|
+
/** Connections page read model and background health checks. */
|
|
64
|
+
channels?: ChannelService;
|
|
62
65
|
email?: ReturnType<typeof emailHostFor> & {
|
|
63
66
|
triggers: EmailTriggers;
|
|
64
67
|
stop: () => Promise<void>;
|
package/dist/server/bootstrap.js
CHANGED
|
@@ -20,6 +20,7 @@ import { GithubPollSource, githubPollIntervalMsFromEnv, } from "../runtime/githu
|
|
|
20
20
|
import { EmailSource } from "../runtime/emailSource.js";
|
|
21
21
|
import { emailHostFor, releaseEmailHost } from "../email/host.js";
|
|
22
22
|
import { EmailTriggers } from "../email/triggers.js";
|
|
23
|
+
import { startChannelService } from "../channels/service.js";
|
|
23
24
|
import { fireTrigger } from "../runtime/triggerRunner.js";
|
|
24
25
|
import { createRunChangeBus, getRunChangeBusFromWrappedStore, isRunStoreWrapped, wrapRunStoreWithChangeBus, } from "../runtime/runChangeBus.js";
|
|
25
26
|
import { createMcpHttpHandler, resolveMcpStateless, } from "../mcp/server.js";
|
|
@@ -331,9 +332,11 @@ export async function bootstrapStageflowHost(options) {
|
|
|
331
332
|
await Promise.all([stopAccountEmail(), emailSource.stop(), scheduleSource.stop(), githubPollSource.stop()]);
|
|
332
333
|
throw error;
|
|
333
334
|
}
|
|
335
|
+
const channels = startChannelService(cwd);
|
|
334
336
|
const stopEmailSource = () => {
|
|
335
337
|
void emailSource.stop();
|
|
336
338
|
void stopAccountEmail();
|
|
339
|
+
void channels.stop();
|
|
337
340
|
};
|
|
338
341
|
const mcpStateless = resolveMcpStateless({
|
|
339
342
|
mcpStateless: options.mcpStateless,
|
|
@@ -374,6 +377,7 @@ export async function bootstrapStageflowHost(options) {
|
|
|
374
377
|
stopGithubPollSource,
|
|
375
378
|
stopEmailSource,
|
|
376
379
|
email: { ...emailHost, triggers: emailTriggers, stop: stopAccountEmail },
|
|
380
|
+
channels,
|
|
377
381
|
storeFilesystem,
|
|
378
382
|
workspaceCatalog,
|
|
379
383
|
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
2
|
+
import type { ChannelService } from "../channels/service.js";
|
|
3
|
+
type Json = (res: ServerResponse, status: number, body: unknown) => void;
|
|
4
|
+
/**
|
|
5
|
+
* Listing, live checks and edits. Request bodies carry secret values on the way in only; nothing
|
|
6
|
+
* the API returns contains one, and failures answer with a code, never the request or its values.
|
|
7
|
+
*/
|
|
8
|
+
export declare function handleChannelRoutes(req: IncomingMessage, method: string, pathname: string, service: ChannelService, res: ServerResponse, json: Json): Promise<boolean>;
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { ChannelError } from "../channels/errors.js";
|
|
2
|
+
import { channelErrorStatus } from "../channels/errorInfo.js";
|
|
3
|
+
const MAX_BODY_BYTES = 65536;
|
|
4
|
+
async function readBody(req) {
|
|
5
|
+
const chunks = [];
|
|
6
|
+
let size = 0;
|
|
7
|
+
for await (const chunk of req) {
|
|
8
|
+
const buffer = typeof chunk === "string" ? Buffer.from(chunk) : chunk;
|
|
9
|
+
size += buffer.length;
|
|
10
|
+
if (size > MAX_BODY_BYTES)
|
|
11
|
+
throw new ChannelError("CHANNEL_RESOURCE_LIMIT");
|
|
12
|
+
chunks.push(buffer);
|
|
13
|
+
}
|
|
14
|
+
if (chunks.length === 0)
|
|
15
|
+
return {};
|
|
16
|
+
try {
|
|
17
|
+
return JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
throw new ChannelError("CHANNEL_INVALID_INPUT");
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Listing, live checks and edits. Request bodies carry secret values on the way in only; nothing
|
|
25
|
+
* the API returns contains one, and failures answer with a code, never the request or its values.
|
|
26
|
+
*/
|
|
27
|
+
export async function handleChannelRoutes(req, method, pathname, service, res, json) {
|
|
28
|
+
const idMatch = pathname.match(/^\/api\/connections\/([^/]+)(?:\/(check|validate-target))?$/);
|
|
29
|
+
const isCollection = pathname === "/api/connections";
|
|
30
|
+
const isTest = pathname === "/api/connections/test";
|
|
31
|
+
const isMeta = pathname === "/api/connections/meta";
|
|
32
|
+
if (!isCollection && !isTest && !isMeta && !idMatch)
|
|
33
|
+
return false;
|
|
34
|
+
try {
|
|
35
|
+
if (isMeta && method === "GET") {
|
|
36
|
+
json(res, 200, service.meta());
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
if (isCollection && method === "GET") {
|
|
40
|
+
json(res, 200, service.list());
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
if (isCollection && method === "POST") {
|
|
44
|
+
json(res, 201, await service.create(await readBody(req)));
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
if (isTest && method === "POST") {
|
|
48
|
+
json(res, 200, await service.test(await readBody(req)));
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
if (idMatch) {
|
|
52
|
+
let id;
|
|
53
|
+
try {
|
|
54
|
+
id = decodeURIComponent(idMatch[1]);
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
throw new ChannelError("CHANNEL_INVALID_INPUT");
|
|
58
|
+
}
|
|
59
|
+
if (idMatch[2] === "check" && method === "POST") {
|
|
60
|
+
const health = await service.checkNow(id);
|
|
61
|
+
if (health)
|
|
62
|
+
json(res, 200, health);
|
|
63
|
+
else
|
|
64
|
+
json(res, 404, { code: "CHANNEL_NOT_FOUND" });
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
if (idMatch[2] === "validate-target" && method === "POST") {
|
|
68
|
+
json(res, 200, service.validateTarget(id, await readBody(req)));
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
if (!idMatch[2] && method === "PATCH") {
|
|
72
|
+
json(res, 200, await service.update(id, await readBody(req)));
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
if (!idMatch[2] && method === "DELETE") {
|
|
76
|
+
await service.remove(id);
|
|
77
|
+
json(res, 200, { removed: true });
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
const code = error instanceof ChannelError ? error.code : "CHANNEL_STORAGE_FAILED";
|
|
84
|
+
json(res, channelErrorStatus(code), { code });
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
return false;
|
|
88
|
+
}
|
package/dist/server/http.js
CHANGED
|
@@ -51,6 +51,7 @@ import { isTaskFile } from "../runtime/taskInput.js";
|
|
|
51
51
|
import { findTokenShapedField, tokenRejectedPayload, } from "../runtime/startPayload.js";
|
|
52
52
|
import { parseAskOperatorAnswer } from "../tools/askOperator.js";
|
|
53
53
|
import { handleEmailRoutes } from "./emailRoutes.js";
|
|
54
|
+
import { handleChannelRoutes } from "./channelRoutes.js";
|
|
54
55
|
import { bootstrapStageflowHost, } from "./bootstrap.js";
|
|
55
56
|
import { assertAllowedHttpAccess, isTrustedLocalHttpRequest, resolveAllowedHosts, } from "./allowedHosts.js";
|
|
56
57
|
import { enforceBearerAuth, loadControlTokens, requiredScopeFor, } from "./controlToken.js";
|
|
@@ -214,6 +215,16 @@ export function createOperatorRoutes(deps) {
|
|
|
214
215
|
try {
|
|
215
216
|
if (await liveView.handle(req, res))
|
|
216
217
|
return true;
|
|
218
|
+
if (pathname === "/api/connections" || pathname.startsWith("/api/connections/")) {
|
|
219
|
+
if (!assertAllowedHttpAccess({ entries: [] }, req, res, { requireOrigin: method !== "GET" }))
|
|
220
|
+
return true;
|
|
221
|
+
if (!boot.channels) {
|
|
222
|
+
json(res, 503, { code: "CHANNEL_CONNECTION_FAILED" });
|
|
223
|
+
return true;
|
|
224
|
+
}
|
|
225
|
+
if (await handleChannelRoutes(req, method, pathname, boot.channels, res, json))
|
|
226
|
+
return true;
|
|
227
|
+
}
|
|
217
228
|
if (pathname.startsWith("/api/email/")) {
|
|
218
229
|
if (!assertAllowedHttpAccess({ entries: [] }, req, res, { requireOrigin: ["POST", "PATCH", "DELETE"].includes(method) }))
|
|
219
230
|
return true;
|
|
@@ -6,12 +6,12 @@ export declare function createSearchEmailTool(email: StageEmail): {
|
|
|
6
6
|
description: string;
|
|
7
7
|
parameters: Type.TObject<Type.TProperties>;
|
|
8
8
|
execute(_toolCallId: string, input: unknown): Promise<{
|
|
9
|
-
isError?: undefined;
|
|
10
9
|
content: {
|
|
11
10
|
type: "text";
|
|
12
11
|
text: string;
|
|
13
12
|
}[];
|
|
14
13
|
details: unknown;
|
|
14
|
+
isError?: undefined;
|
|
15
15
|
} | {
|
|
16
16
|
content: {
|
|
17
17
|
type: "text";
|
|
@@ -31,12 +31,12 @@ export declare function createGetEmailTool(email: StageEmail): {
|
|
|
31
31
|
description: string;
|
|
32
32
|
parameters: Type.TObject<Type.TProperties>;
|
|
33
33
|
execute(_toolCallId: string, input: unknown): Promise<{
|
|
34
|
-
isError?: undefined;
|
|
35
34
|
content: {
|
|
36
35
|
type: "text";
|
|
37
36
|
text: string;
|
|
38
37
|
}[];
|
|
39
38
|
details: unknown;
|
|
39
|
+
isError?: undefined;
|
|
40
40
|
} | {
|
|
41
41
|
content: {
|
|
42
42
|
type: "text";
|
|
@@ -56,12 +56,12 @@ export declare function createDownloadEmailAttachmentTool(email: StageEmail): {
|
|
|
56
56
|
description: string;
|
|
57
57
|
parameters: Type.TObject<Type.TProperties>;
|
|
58
58
|
execute(_toolCallId: string, input: unknown): Promise<{
|
|
59
|
-
isError?: undefined;
|
|
60
59
|
content: {
|
|
61
60
|
type: "text";
|
|
62
61
|
text: string;
|
|
63
62
|
}[];
|
|
64
63
|
details: unknown;
|
|
64
|
+
isError?: undefined;
|
|
65
65
|
} | {
|
|
66
66
|
content: {
|
|
67
67
|
type: "text";
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Type } from "typebox";
|
|
2
|
+
import { type StageChannels } from "../channels/host.js";
|
|
3
|
+
export declare function createSendMessageTool(channels: StageChannels): {
|
|
4
|
+
name: string;
|
|
5
|
+
label: string;
|
|
6
|
+
description: string;
|
|
7
|
+
parameters: Type.TObject<{
|
|
8
|
+
channel: Type.TOptional<Type.TString>;
|
|
9
|
+
text: Type.TString;
|
|
10
|
+
subject: Type.TOptional<Type.TString>;
|
|
11
|
+
operationKey: Type.TString;
|
|
12
|
+
}>;
|
|
13
|
+
execute(_toolCallId: string, input: unknown): Promise<{
|
|
14
|
+
content: {
|
|
15
|
+
type: "text";
|
|
16
|
+
text: string;
|
|
17
|
+
}[];
|
|
18
|
+
details: unknown;
|
|
19
|
+
isError?: boolean;
|
|
20
|
+
}>;
|
|
21
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Type } from "typebox";
|
|
2
|
+
import { MAX_MESSAGE_TEXT, channelFault } from "../channels/host.js";
|
|
3
|
+
export function createSendMessageTool(channels) {
|
|
4
|
+
return {
|
|
5
|
+
name: "send_message",
|
|
6
|
+
label: "Send message",
|
|
7
|
+
description: "Send a message on one of this stage's declared communication channels. The recipient is fixed by Stageflow configuration and cannot be chosen. Pass the channel alias when the stage declares more than one. Reuse the same operationKey when retrying the same message. Acceptance means provider submission, not delivery or that anyone read it. Never retry an unknown outcome with a new key. Do not put secrets in a message.",
|
|
8
|
+
parameters: Type.Object({
|
|
9
|
+
channel: Type.Optional(Type.String()),
|
|
10
|
+
text: Type.String({ maxLength: MAX_MESSAGE_TEXT }),
|
|
11
|
+
subject: Type.Optional(Type.String()),
|
|
12
|
+
operationKey: Type.String(),
|
|
13
|
+
}),
|
|
14
|
+
async execute(_toolCallId, input) {
|
|
15
|
+
try {
|
|
16
|
+
const receipt = await channels.send(input);
|
|
17
|
+
return { content: [{ type: "text", text: JSON.stringify(receipt) }], details: receipt };
|
|
18
|
+
}
|
|
19
|
+
catch (error) {
|
|
20
|
+
const details = channelFault(error);
|
|
21
|
+
return { content: [{ type: "text", text: JSON.stringify(details) }], details, isError: true };
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
}
|
package/dist/types/pipeline.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { LoadedStageConfig, StageBrowserConfig, StageGateKind, StageIoYaml } from "./stage.js";
|
|
1
|
+
import type { LoadedStageConfig, StageChannel, StageBrowserConfig, StageGateKind, StageIoYaml } from "./stage.js";
|
|
2
2
|
import type { CompletionContract, RecoveryPolicy } from "./completion.js";
|
|
3
3
|
import type { StageSecretDecl } from "../runtime/stageSecretDecl.js";
|
|
4
4
|
import type { ToolRequirement } from "../config/toolRequires.js";
|
|
@@ -13,6 +13,8 @@ export type PipelineConfig = {
|
|
|
13
13
|
schemas?: Record<string, unknown>;
|
|
14
14
|
/** Declared toolchain binaries this pipeline needs (Slot 9). */
|
|
15
15
|
requires?: ToolRequirement[];
|
|
16
|
+
/** Default channels inherited by every stage that does not override them. */
|
|
17
|
+
channels?: StageChannel[];
|
|
16
18
|
};
|
|
17
19
|
/** An inline pipeline definition authored directly in a `start_run` call, with no backing file. */
|
|
18
20
|
export type InlinePipelineDefinition = {
|
|
@@ -22,6 +24,7 @@ export type InlinePipelineDefinition = {
|
|
|
22
24
|
model?: unknown;
|
|
23
25
|
schemas?: unknown;
|
|
24
26
|
requires?: unknown;
|
|
27
|
+
channels?: unknown;
|
|
25
28
|
};
|
|
26
29
|
export type PipelineForkConfig = {
|
|
27
30
|
select: "one" | "subset";
|
package/dist/types/stage.d.ts
CHANGED
|
@@ -36,6 +36,17 @@ export type StageBrowserConfig = {
|
|
|
36
36
|
logged_out_url?: string | string[];
|
|
37
37
|
};
|
|
38
38
|
};
|
|
39
|
+
export type StageChannelOperation = "send";
|
|
40
|
+
/** A pinned communication target for a stage. Targets are validated per provider. */
|
|
41
|
+
export type StageChannel = {
|
|
42
|
+
alias?: string;
|
|
43
|
+
connection: string;
|
|
44
|
+
operations: StageChannelOperation[];
|
|
45
|
+
to: string | string[];
|
|
46
|
+
cc?: string[];
|
|
47
|
+
/** Provider-specific message template, such as a pinned WhatsApp template. Validated by the provider. */
|
|
48
|
+
template?: Record<string, unknown>;
|
|
49
|
+
};
|
|
39
50
|
export type StageConfig = {
|
|
40
51
|
id: string;
|
|
41
52
|
system_prompt: string;
|
|
@@ -56,6 +67,8 @@ export type StageConfig = {
|
|
|
56
67
|
operations: ("send" | "reply" | "getMessage" | "search" | "downloadAttachment")[];
|
|
57
68
|
}[];
|
|
58
69
|
mcp?: string[];
|
|
70
|
+
/** Communication channels this stage may send on. Inherits pipeline `channels` defaults. */
|
|
71
|
+
channels?: StageChannel[];
|
|
59
72
|
/** Declared Host secret names this stage may receive (Slot 6). */
|
|
60
73
|
secrets?: StageSecretDecl[];
|
|
61
74
|
/** Declared toolchain binaries this stage needs (Slot 9). */
|