Skip to content

Commit 3b4de1a

Browse files
committed
fix(cycles): split reply and gateway leaf seams
1 parent 370efaa commit 3b4de1a

8 files changed

Lines changed: 51 additions & 33 deletions

File tree

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import type { ReplyDispatcher } from "./reply/reply-dispatcher.js";
2+
3+
export async function withReplyDispatcher<T>(params: {
4+
dispatcher: ReplyDispatcher;
5+
run: () => Promise<T>;
6+
onSettled?: () => void | Promise<void>;
7+
}): Promise<T> {
8+
try {
9+
return await params.run();
10+
} finally {
11+
// Ensure dispatcher reservations are always released on every exit path.
12+
params.dispatcher.markComplete();
13+
try {
14+
await params.dispatcher.waitForIdle();
15+
} finally {
16+
await params.onSettled?.();
17+
}
18+
}
19+
}

src/auto-reply/dispatch.ts

Lines changed: 6 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import type { OpenClawConfig } from "../config/types.openclaw.js";
2+
import { withReplyDispatcher } from "./dispatch-dispatcher.js";
23
import { dispatchReplyFromConfig } from "./reply/dispatch-from-config.js";
34
import type { DispatchFromConfigResult } from "./reply/dispatch-from-config.types.js";
5+
import type { GetReplyFromConfig } from "./reply/get-reply.types.js";
46
import { finalizeInboundContext } from "./reply/inbound-context.js";
57
import {
68
createReplyDispatcher,
@@ -13,31 +15,14 @@ import type { FinalizedMsgContext, MsgContext } from "./templating.js";
1315
import type { GetReplyOptions } from "./types.js";
1416

1517
export type DispatchInboundResult = DispatchFromConfigResult;
16-
17-
export async function withReplyDispatcher<T>(params: {
18-
dispatcher: ReplyDispatcher;
19-
run: () => Promise<T>;
20-
onSettled?: () => void | Promise<void>;
21-
}): Promise<T> {
22-
try {
23-
return await params.run();
24-
} finally {
25-
// Ensure dispatcher reservations are always released on every exit path.
26-
params.dispatcher.markComplete();
27-
try {
28-
await params.dispatcher.waitForIdle();
29-
} finally {
30-
await params.onSettled?.();
31-
}
32-
}
33-
}
18+
export { withReplyDispatcher } from "./dispatch-dispatcher.js";
3419

3520
export async function dispatchInboundMessage(params: {
3621
ctx: MsgContext | FinalizedMsgContext;
3722
cfg: OpenClawConfig;
3823
dispatcher: ReplyDispatcher;
3924
replyOptions?: Omit<GetReplyOptions, "onToolResult" | "onBlockReply">;
40-
replyResolver?: typeof import("./reply.js").getReplyFromConfig;
25+
replyResolver?: GetReplyFromConfig;
4126
}): Promise<DispatchInboundResult> {
4227
const finalized = finalizeInboundContext(params.ctx);
4328
return await withReplyDispatcher({
@@ -58,7 +43,7 @@ export async function dispatchInboundMessageWithBufferedDispatcher(params: {
5843
cfg: OpenClawConfig;
5944
dispatcherOptions: ReplyDispatcherWithTypingOptions;
6045
replyOptions?: Omit<GetReplyOptions, "onToolResult" | "onBlockReply">;
61-
replyResolver?: typeof import("./reply.js").getReplyFromConfig;
46+
replyResolver?: GetReplyFromConfig;
6247
}): Promise<DispatchInboundResult> {
6348
const { dispatcher, replyOptions, markDispatchIdle, markRunComplete } =
6449
createReplyDispatcherWithTyping(params.dispatcherOptions);
@@ -84,7 +69,7 @@ export async function dispatchInboundMessageWithDispatcher(params: {
8469
cfg: OpenClawConfig;
8570
dispatcherOptions: ReplyDispatcherOptions;
8671
replyOptions?: Omit<GetReplyOptions, "onToolResult" | "onBlockReply">;
87-
replyResolver?: typeof import("./reply.js").getReplyFromConfig;
72+
replyResolver?: GetReplyFromConfig;
8873
}): Promise<DispatchInboundResult> {
8974
const dispatcher = createReplyDispatcher(params.dispatcherOptions);
9075
return await dispatchInboundMessage({
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import type { OpenClawConfig } from "../../config/types.openclaw.js";
2+
import type { MsgContext } from "../templating.js";
3+
import type { GetReplyOptions, ReplyPayload } from "../types.js";
4+
5+
export type GetReplyFromConfig = (
6+
ctx: MsgContext,
7+
opts?: GetReplyOptions,
8+
configOverride?: OpenClawConfig,
9+
) => Promise<ReplyPayload | ReplyPayload[] | undefined>;

src/auto-reply/reply/provider-dispatcher.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
} from "../dispatch.js";
77
import type { FinalizedMsgContext, MsgContext } from "../templating.js";
88
import type { GetReplyOptions } from "../types.js";
9+
import type { GetReplyFromConfig } from "./get-reply.types.js";
910
import type {
1011
ReplyDispatcherOptions,
1112
ReplyDispatcherWithTypingOptions,
@@ -16,7 +17,7 @@ export async function dispatchReplyWithBufferedBlockDispatcher(params: {
1617
cfg: OpenClawConfig;
1718
dispatcherOptions: ReplyDispatcherWithTypingOptions;
1819
replyOptions?: Omit<GetReplyOptions, "onToolResult" | "onBlockReply">;
19-
replyResolver?: typeof import("../reply.js").getReplyFromConfig;
20+
replyResolver?: GetReplyFromConfig;
2021
}): Promise<DispatchInboundResult> {
2122
return await dispatchInboundMessageWithBufferedDispatcher({
2223
ctx: params.ctx,
@@ -32,7 +33,7 @@ export async function dispatchReplyWithDispatcher(params: {
3233
cfg: OpenClawConfig;
3334
dispatcherOptions: ReplyDispatcherOptions;
3435
replyOptions?: Omit<GetReplyOptions, "onToolResult" | "onBlockReply">;
35-
replyResolver?: typeof import("../reply.js").getReplyFromConfig;
36+
replyResolver?: GetReplyFromConfig;
3637
}): Promise<DispatchInboundResult> {
3738
return await dispatchInboundMessageWithDispatcher({
3839
ctx: params.ctx,

src/gateway/client-bootstrap.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type { OpenClawConfig } from "../config/types.openclaw.js";
2-
import { buildGatewayConnectionDetails } from "./call.js";
3-
import type { ExplicitGatewayAuth } from "./call.js";
42
import { resolveGatewayConnectionAuth } from "./connection-auth.js";
3+
import { buildGatewayConnectionDetailsWithResolvers } from "./connection-details.js";
4+
import type { ExplicitGatewayAuth } from "./credentials.js";
55

66
export function resolveGatewayUrlOverrideSource(urlSource: string): "cli" | "env" | undefined {
77
if (urlSource === "cli --url") {
@@ -26,7 +26,7 @@ export async function resolveGatewayClientBootstrap(params: {
2626
password?: string;
2727
};
2828
}> {
29-
const connection = buildGatewayConnectionDetails({
29+
const connection = buildGatewayConnectionDetailsWithResolvers({
3030
config: params.config,
3131
url: params.gatewayUrl,
3232
});

src/gateway/net.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import fs from "node:fs";
22
import type { IncomingMessage } from "node:http";
33
import net from "node:net";
4+
import type { GatewayBindMode } from "../config/types.gateway.js";
45
import {
56
pickMatchingExternalInterfaceAddress,
67
readNetworkInterfaces,
@@ -295,7 +296,7 @@ export function __resetContainerCacheForTest(): void {
295296
* @returns The bind address to use (never null)
296297
*/
297298
export async function resolveGatewayBindHost(
298-
bind: import("../config/config.js").GatewayBindMode | undefined,
299+
bind: GatewayBindMode | undefined,
299300
customHost?: string,
300301
): Promise<string> {
301302
const mode = bind ?? "loopback";
@@ -365,9 +366,7 @@ export async function resolveGatewayBindHost(
365366
* environment as the eventual bind decision. Host-side diagnostics should keep
366367
* their own explicit defaults instead of inferring from the caller process.
367368
*/
368-
export function defaultGatewayBindMode(
369-
tailscaleMode?: string,
370-
): import("../config/config.js").GatewayBindMode {
369+
export function defaultGatewayBindMode(tailscaleMode?: string): GatewayBindMode {
371370
if (tailscaleMode && tailscaleMode !== "off") {
372371
return "loopback";
373372
}

src/infra/approval-view-model.types.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1+
import type { InteractiveReplyButton } from "../interactive/payload.js";
12
import type { ChannelApprovalKind } from "./approval-types.js";
2-
import type { ExecApprovalActionDescriptor } from "./exec-approval-reply.js";
33
import type {
44
ExecApprovalDecision,
55
ExecApprovalRequest,
@@ -9,7 +9,12 @@ import type { PluginApprovalRequest, PluginApprovalResolved } from "./plugin-app
99

1010
type ApprovalPhase = "pending" | "resolved" | "expired";
1111

12-
export type ApprovalActionView = ExecApprovalActionDescriptor;
12+
export type ApprovalActionView = {
13+
decision: ExecApprovalDecision;
14+
label: string;
15+
style: NonNullable<InteractiveReplyButton["style"]>;
16+
command: string;
17+
};
1318

1419
export type ApprovalMetadataView = {
1520
label: string;

src/plugins/runtime/types-channel.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ export type PluginRuntimeChannel = {
8484
resolveEffectiveMessagesConfig: typeof import("../../agents/identity.js").resolveEffectiveMessagesConfig;
8585
resolveHumanDelayConfig: typeof import("../../agents/identity.js").resolveHumanDelayConfig;
8686
dispatchReplyFromConfig: import("../../auto-reply/reply/dispatch-from-config.types.js").DispatchReplyFromConfig;
87-
withReplyDispatcher: typeof import("../../auto-reply/dispatch.js").withReplyDispatcher;
87+
withReplyDispatcher: typeof import("../../auto-reply/dispatch-dispatcher.js").withReplyDispatcher;
8888
finalizeInboundContext: typeof import("../../auto-reply/reply/inbound-context.js").finalizeInboundContext;
8989
formatAgentEnvelope: typeof import("../../auto-reply/envelope.js").formatAgentEnvelope;
9090
/** @deprecated Prefer `BodyForAgent` + structured user-context blocks (do not build plaintext envelopes for prompts). */

0 commit comments

Comments
 (0)