fix: thread accountId through subagent announce
Co-authored-by: Adam Holt <adam91holt@users.noreply.github.com>
This commit is contained in:
@@ -29,6 +29,7 @@
|
|||||||
- Health: add per-agent session summaries and account-level health details, and allow selective probes. (#1047) — thanks @gumadeiras.
|
- Health: add per-agent session summaries and account-level health details, and allow selective probes. (#1047) — thanks @gumadeiras.
|
||||||
|
|
||||||
### Fixes
|
### Fixes
|
||||||
|
- Sub-agents: route announce delivery through the correct channel account IDs. (#1061, #1058) — thanks @adam91holt.
|
||||||
- Messages: `/stop` now hard-aborts queued followups and sub-agent runs; suppress zero-count stop notes.
|
- Messages: `/stop` now hard-aborts queued followups and sub-agent runs; suppress zero-count stop notes.
|
||||||
- Sessions: reset `compactionCount` on `/new` and `/reset`, and preserve `sessions.json` file mode (0600).
|
- Sessions: reset `compactionCount` on `/new` and `/reset`, and preserve `sessions.json` file mode (0600).
|
||||||
- Sessions: repair orphaned user turns before embedded prompts.
|
- Sessions: repair orphaned user turns before embedded prompts.
|
||||||
|
|||||||
@@ -108,6 +108,7 @@ export function createClawdbotTools(options?: {
|
|||||||
createSessionsSpawnTool({
|
createSessionsSpawnTool({
|
||||||
agentSessionKey: options?.agentSessionKey,
|
agentSessionKey: options?.agentSessionKey,
|
||||||
agentChannel: options?.agentChannel,
|
agentChannel: options?.agentChannel,
|
||||||
|
agentAccountId: options?.agentAccountId,
|
||||||
sandboxed: options?.sandboxed,
|
sandboxed: options?.sandboxed,
|
||||||
}),
|
}),
|
||||||
createSessionStatusTool({
|
createSessionStatusTool({
|
||||||
|
|||||||
@@ -306,6 +306,9 @@ async function sendAnnounce(item: AnnounceQueueItem) {
|
|||||||
params: {
|
params: {
|
||||||
sessionKey: item.sessionKey,
|
sessionKey: item.sessionKey,
|
||||||
message: item.prompt,
|
message: item.prompt,
|
||||||
|
channel: item.originatingChannel,
|
||||||
|
accountId: item.originatingAccountId,
|
||||||
|
to: item.originatingTo,
|
||||||
deliver: true,
|
deliver: true,
|
||||||
idempotencyKey: crypto.randomUUID(),
|
idempotencyKey: crypto.randomUUID(),
|
||||||
},
|
},
|
||||||
@@ -348,6 +351,7 @@ async function maybeQueueSubagentAnnounce(params: {
|
|||||||
requesterSessionKey: string;
|
requesterSessionKey: string;
|
||||||
triggerMessage: string;
|
triggerMessage: string;
|
||||||
summaryLine?: string;
|
summaryLine?: string;
|
||||||
|
requesterAccountId?: string;
|
||||||
}): Promise<"steered" | "queued" | "none"> {
|
}): Promise<"steered" | "queued" | "none"> {
|
||||||
const { cfg, entry } = loadRequesterSessionEntry(params.requesterSessionKey);
|
const { cfg, entry } = loadRequesterSessionEntry(params.requesterSessionKey);
|
||||||
const canonicalKey = resolveRequesterStoreKey(cfg, params.requesterSessionKey);
|
const canonicalKey = resolveRequesterStoreKey(cfg, params.requesterSessionKey);
|
||||||
@@ -382,7 +386,7 @@ async function maybeQueueSubagentAnnounce(params: {
|
|||||||
sessionKey: canonicalKey,
|
sessionKey: canonicalKey,
|
||||||
originatingChannel: entry?.lastChannel,
|
originatingChannel: entry?.lastChannel,
|
||||||
originatingTo: entry?.lastTo,
|
originatingTo: entry?.lastTo,
|
||||||
originatingAccountId: entry?.lastAccountId,
|
originatingAccountId: entry?.lastAccountId ?? params.requesterAccountId,
|
||||||
},
|
},
|
||||||
queueSettings,
|
queueSettings,
|
||||||
);
|
);
|
||||||
@@ -505,6 +509,7 @@ export async function runSubagentAnnounceFlow(params: {
|
|||||||
childRunId: string;
|
childRunId: string;
|
||||||
requesterSessionKey: string;
|
requesterSessionKey: string;
|
||||||
requesterChannel?: string;
|
requesterChannel?: string;
|
||||||
|
requesterAccountId?: string;
|
||||||
requesterDisplayKey: string;
|
requesterDisplayKey: string;
|
||||||
task: string;
|
task: string;
|
||||||
timeoutMs: number;
|
timeoutMs: number;
|
||||||
@@ -600,6 +605,7 @@ export async function runSubagentAnnounceFlow(params: {
|
|||||||
requesterSessionKey: params.requesterSessionKey,
|
requesterSessionKey: params.requesterSessionKey,
|
||||||
triggerMessage,
|
triggerMessage,
|
||||||
summaryLine: taskLabel,
|
summaryLine: taskLabel,
|
||||||
|
requesterAccountId: params.requesterAccountId,
|
||||||
});
|
});
|
||||||
if (queued === "steered") {
|
if (queued === "steered") {
|
||||||
didAnnounce = true;
|
didAnnounce = true;
|
||||||
@@ -617,6 +623,8 @@ export async function runSubagentAnnounceFlow(params: {
|
|||||||
sessionKey: params.requesterSessionKey,
|
sessionKey: params.requesterSessionKey,
|
||||||
message: triggerMessage,
|
message: triggerMessage,
|
||||||
deliver: true,
|
deliver: true,
|
||||||
|
channel: params.requesterChannel,
|
||||||
|
accountId: params.requesterAccountId,
|
||||||
idempotencyKey: crypto.randomUUID(),
|
idempotencyKey: crypto.randomUUID(),
|
||||||
},
|
},
|
||||||
expectFinal: true,
|
expectFinal: true,
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ export type SubagentRunRecord = {
|
|||||||
childSessionKey: string;
|
childSessionKey: string;
|
||||||
requesterSessionKey: string;
|
requesterSessionKey: string;
|
||||||
requesterChannel?: string;
|
requesterChannel?: string;
|
||||||
|
requesterAccountId?: string;
|
||||||
requesterDisplayKey: string;
|
requesterDisplayKey: string;
|
||||||
task: string;
|
task: string;
|
||||||
cleanup: "delete" | "keep";
|
cleanup: "delete" | "keep";
|
||||||
@@ -59,6 +60,7 @@ function resumeSubagentRun(runId: string) {
|
|||||||
childRunId: entry.runId,
|
childRunId: entry.runId,
|
||||||
requesterSessionKey: entry.requesterSessionKey,
|
requesterSessionKey: entry.requesterSessionKey,
|
||||||
requesterChannel: entry.requesterChannel,
|
requesterChannel: entry.requesterChannel,
|
||||||
|
requesterAccountId: entry.requesterAccountId,
|
||||||
requesterDisplayKey: entry.requesterDisplayKey,
|
requesterDisplayKey: entry.requesterDisplayKey,
|
||||||
task: entry.task,
|
task: entry.task,
|
||||||
timeoutMs: 30_000,
|
timeoutMs: 30_000,
|
||||||
@@ -199,6 +201,7 @@ function ensureListener() {
|
|||||||
childRunId: entry.runId,
|
childRunId: entry.runId,
|
||||||
requesterSessionKey: entry.requesterSessionKey,
|
requesterSessionKey: entry.requesterSessionKey,
|
||||||
requesterChannel: entry.requesterChannel,
|
requesterChannel: entry.requesterChannel,
|
||||||
|
requesterAccountId: entry.requesterAccountId,
|
||||||
requesterDisplayKey: entry.requesterDisplayKey,
|
requesterDisplayKey: entry.requesterDisplayKey,
|
||||||
task: entry.task,
|
task: entry.task,
|
||||||
timeoutMs: 30_000,
|
timeoutMs: 30_000,
|
||||||
@@ -248,6 +251,7 @@ export function registerSubagentRun(params: {
|
|||||||
childSessionKey: string;
|
childSessionKey: string;
|
||||||
requesterSessionKey: string;
|
requesterSessionKey: string;
|
||||||
requesterChannel?: string;
|
requesterChannel?: string;
|
||||||
|
requesterAccountId?: string;
|
||||||
requesterDisplayKey: string;
|
requesterDisplayKey: string;
|
||||||
task: string;
|
task: string;
|
||||||
cleanup: "delete" | "keep";
|
cleanup: "delete" | "keep";
|
||||||
@@ -264,6 +268,7 @@ export function registerSubagentRun(params: {
|
|||||||
childSessionKey: params.childSessionKey,
|
childSessionKey: params.childSessionKey,
|
||||||
requesterSessionKey: params.requesterSessionKey,
|
requesterSessionKey: params.requesterSessionKey,
|
||||||
requesterChannel: params.requesterChannel,
|
requesterChannel: params.requesterChannel,
|
||||||
|
requesterAccountId: params.requesterAccountId,
|
||||||
requesterDisplayKey: params.requesterDisplayKey,
|
requesterDisplayKey: params.requesterDisplayKey,
|
||||||
task: params.task,
|
task: params.task,
|
||||||
cleanup: params.cleanup,
|
cleanup: params.cleanup,
|
||||||
@@ -318,6 +323,7 @@ async function waitForSubagentCompletion(runId: string, waitTimeoutMs: number) {
|
|||||||
childRunId: entry.runId,
|
childRunId: entry.runId,
|
||||||
requesterSessionKey: entry.requesterSessionKey,
|
requesterSessionKey: entry.requesterSessionKey,
|
||||||
requesterChannel: entry.requesterChannel,
|
requesterChannel: entry.requesterChannel,
|
||||||
|
requesterAccountId: entry.requesterAccountId,
|
||||||
requesterDisplayKey: entry.requesterDisplayKey,
|
requesterDisplayKey: entry.requesterDisplayKey,
|
||||||
task: entry.task,
|
task: entry.task,
|
||||||
timeoutMs: 30_000,
|
timeoutMs: 30_000,
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ function normalizeModelSelection(value: unknown): string | undefined {
|
|||||||
export function createSessionsSpawnTool(opts?: {
|
export function createSessionsSpawnTool(opts?: {
|
||||||
agentSessionKey?: string;
|
agentSessionKey?: string;
|
||||||
agentChannel?: GatewayMessageChannel;
|
agentChannel?: GatewayMessageChannel;
|
||||||
|
agentAccountId?: string;
|
||||||
sandboxed?: boolean;
|
sandboxed?: boolean;
|
||||||
}): AnyAgentTool {
|
}): AnyAgentTool {
|
||||||
return {
|
return {
|
||||||
@@ -206,6 +207,7 @@ export function createSessionsSpawnTool(opts?: {
|
|||||||
childSessionKey,
|
childSessionKey,
|
||||||
requesterSessionKey: requesterInternalKey,
|
requesterSessionKey: requesterInternalKey,
|
||||||
requesterChannel: opts?.agentChannel,
|
requesterChannel: opts?.agentChannel,
|
||||||
|
requesterAccountId: opts?.agentAccountId,
|
||||||
requesterDisplayKey,
|
requesterDisplayKey,
|
||||||
task,
|
task,
|
||||||
cleanup,
|
cleanup,
|
||||||
|
|||||||
@@ -48,13 +48,19 @@ export async function deliverAgentCommandResult(params: {
|
|||||||
|
|
||||||
const targetMode: ChannelOutboundTargetMode =
|
const targetMode: ChannelOutboundTargetMode =
|
||||||
opts.deliveryTargetMode ?? (opts.to ? "explicit" : "implicit");
|
opts.deliveryTargetMode ?? (opts.to ? "explicit" : "implicit");
|
||||||
|
const resolvedAccountId =
|
||||||
|
typeof opts.accountId === "string" && opts.accountId.trim()
|
||||||
|
? opts.accountId.trim()
|
||||||
|
: targetMode === "implicit"
|
||||||
|
? sessionEntry?.lastAccountId
|
||||||
|
: undefined;
|
||||||
const resolvedTarget =
|
const resolvedTarget =
|
||||||
deliver && isDeliveryChannelKnown && deliveryChannel
|
deliver && isDeliveryChannelKnown && deliveryChannel
|
||||||
? resolveOutboundTarget({
|
? resolveOutboundTarget({
|
||||||
channel: deliveryChannel,
|
channel: deliveryChannel,
|
||||||
to: opts.to,
|
to: opts.to,
|
||||||
cfg,
|
cfg,
|
||||||
accountId: targetMode === "implicit" ? sessionEntry?.lastAccountId : undefined,
|
accountId: resolvedAccountId,
|
||||||
mode: targetMode,
|
mode: targetMode,
|
||||||
})
|
})
|
||||||
: null;
|
: null;
|
||||||
@@ -112,6 +118,7 @@ export async function deliverAgentCommandResult(params: {
|
|||||||
cfg,
|
cfg,
|
||||||
channel: deliveryChannel,
|
channel: deliveryChannel,
|
||||||
to: deliveryTarget,
|
to: deliveryTarget,
|
||||||
|
accountId: resolvedAccountId,
|
||||||
payloads: deliveryPayloads,
|
payloads: deliveryPayloads,
|
||||||
bestEffort: bestEffortDeliver,
|
bestEffort: bestEffortDeliver,
|
||||||
onError: (err) => logDeliveryError(err),
|
onError: (err) => logDeliveryError(err),
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ export type AgentCommandOpts = {
|
|||||||
/** Message channel context (webchat|voicewake|whatsapp|...). */
|
/** Message channel context (webchat|voicewake|whatsapp|...). */
|
||||||
messageChannel?: string;
|
messageChannel?: string;
|
||||||
channel?: string; // delivery channel (whatsapp|telegram|...)
|
channel?: string; // delivery channel (whatsapp|telegram|...)
|
||||||
|
/** Account ID for multi-account channel routing (e.g., WhatsApp account). */
|
||||||
|
accountId?: string;
|
||||||
deliveryTargetMode?: ChannelOutboundTargetMode;
|
deliveryTargetMode?: ChannelOutboundTargetMode;
|
||||||
bestEffortDeliver?: boolean;
|
bestEffortDeliver?: boolean;
|
||||||
abortSignal?: AbortSignal;
|
abortSignal?: AbortSignal;
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ export const AgentParamsSchema = Type.Object(
|
|||||||
deliver: Type.Optional(Type.Boolean()),
|
deliver: Type.Optional(Type.Boolean()),
|
||||||
attachments: Type.Optional(Type.Array(Type.Unknown())),
|
attachments: Type.Optional(Type.Array(Type.Unknown())),
|
||||||
channel: Type.Optional(Type.String()),
|
channel: Type.Optional(Type.String()),
|
||||||
|
accountId: Type.Optional(Type.String()),
|
||||||
timeout: Type.Optional(Type.Integer({ minimum: 0 })),
|
timeout: Type.Optional(Type.Integer({ minimum: 0 })),
|
||||||
lane: Type.Optional(Type.String()),
|
lane: Type.Optional(Type.String()),
|
||||||
extraSystemPrompt: Type.Optional(Type.String()),
|
extraSystemPrompt: Type.Optional(Type.String()),
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ export const agentHandlers: GatewayRequestHandlers = {
|
|||||||
content?: unknown;
|
content?: unknown;
|
||||||
}>;
|
}>;
|
||||||
channel?: string;
|
channel?: string;
|
||||||
|
accountId?: string;
|
||||||
lane?: string;
|
lane?: string;
|
||||||
extraSystemPrompt?: string;
|
extraSystemPrompt?: string;
|
||||||
idempotencyKey: string;
|
idempotencyKey: string;
|
||||||
@@ -199,6 +200,10 @@ export const agentHandlers: GatewayRequestHandlers = {
|
|||||||
|
|
||||||
const lastChannel = sessionEntry?.lastChannel;
|
const lastChannel = sessionEntry?.lastChannel;
|
||||||
const lastTo = typeof sessionEntry?.lastTo === "string" ? sessionEntry.lastTo.trim() : "";
|
const lastTo = typeof sessionEntry?.lastTo === "string" ? sessionEntry.lastTo.trim() : "";
|
||||||
|
const resolvedAccountId =
|
||||||
|
typeof request.accountId === "string" && request.accountId.trim()
|
||||||
|
? request.accountId.trim()
|
||||||
|
: sessionEntry?.lastAccountId;
|
||||||
|
|
||||||
const wantsDelivery = request.deliver === true;
|
const wantsDelivery = request.deliver === true;
|
||||||
|
|
||||||
@@ -235,7 +240,7 @@ export const agentHandlers: GatewayRequestHandlers = {
|
|||||||
const fallback = resolveOutboundTarget({
|
const fallback = resolveOutboundTarget({
|
||||||
channel: resolvedChannel,
|
channel: resolvedChannel,
|
||||||
cfg,
|
cfg,
|
||||||
accountId: sessionEntry?.lastAccountId ?? undefined,
|
accountId: resolvedAccountId,
|
||||||
mode: "implicit",
|
mode: "implicit",
|
||||||
});
|
});
|
||||||
if (fallback.ok) {
|
if (fallback.ok) {
|
||||||
@@ -269,6 +274,7 @@ export const agentHandlers: GatewayRequestHandlers = {
|
|||||||
deliver,
|
deliver,
|
||||||
deliveryTargetMode,
|
deliveryTargetMode,
|
||||||
channel: resolvedChannel,
|
channel: resolvedChannel,
|
||||||
|
accountId: resolvedAccountId,
|
||||||
timeout: request.timeout?.toString(),
|
timeout: request.timeout?.toString(),
|
||||||
bestEffortDeliver,
|
bestEffortDeliver,
|
||||||
messageChannel: resolvedChannel,
|
messageChannel: resolvedChannel,
|
||||||
|
|||||||
Reference in New Issue
Block a user