mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-24 19:35:28 -06:00
feat(discord): show subagent progress (#95604)
* feat(discord): show subagent progress Co-authored-by: Kyle Klouzal <kklouzal@users.noreply.github.com> * fix(discord): serialize progress cleanup ownership Co-authored-by: Kyle Klouzal <kklouzal@users.noreply.github.com> * test(discord): type progress state fixtures * refactor(discord): split subagent progress state * fix(discord): persist terminal progress outcomes * fix(discord): narrow persisted cleanup rows * refactor(discord): keep progress internals private * perf(discord): preserve lazy progress loading * chore(plugin-sdk): refresh API baseline after rebase --------- Co-authored-by: Peter Steinberger <steipete@gmail.com> Co-authored-by: Kyle Klouzal <kklouzal@users.noreply.github.com>
This commit is contained in:
@@ -31,7 +31,12 @@ const BUNDLED_TYPED_HOOK_REGISTRATION_GUARDS = {
|
||||
"extensions/active-memory/index.ts": ["before_prompt_build"],
|
||||
"extensions/codex/index.ts": ["after_compaction", "inbound_claim", "session_end"],
|
||||
"extensions/diffs/src/plugin.ts": ["before_prompt_build"],
|
||||
"extensions/discord/subagent-hooks-api.ts": ["subagent_delivery_target", "subagent_ended"],
|
||||
"extensions/discord/subagent-hooks-api.ts": [
|
||||
"gateway_start",
|
||||
"subagent_delivery_target",
|
||||
"subagent_ended",
|
||||
"subagent_progress",
|
||||
],
|
||||
"extensions/feishu/subagent-hooks-api.ts": ["subagent_delivery_target", "subagent_ended"],
|
||||
"extensions/matrix/subagent-hooks-api.ts": ["subagent_delivery_target", "subagent_ended"],
|
||||
"extensions/memory-core/src/dreaming.ts": ["before_agent_reply", "gateway_start", "gateway_stop"],
|
||||
|
||||
@@ -109,6 +109,7 @@ export type PluginHookName =
|
||||
| "subagent_spawning"
|
||||
| "subagent_delivery_target"
|
||||
| "subagent_spawned"
|
||||
| "subagent_progress"
|
||||
| "subagent_ended"
|
||||
/** @deprecated Use gateway_stop. */
|
||||
| "deactivate"
|
||||
@@ -153,6 +154,7 @@ const PLUGIN_HOOK_NAMES = [
|
||||
"subagent_spawning",
|
||||
"subagent_delivery_target",
|
||||
"subagent_spawned",
|
||||
"subagent_progress",
|
||||
"subagent_ended",
|
||||
"deactivate",
|
||||
"gateway_start",
|
||||
@@ -747,17 +749,23 @@ export type PluginHookSubagentContext = {
|
||||
|
||||
type PluginHookSubagentTargetKind = "subagent" | "acp";
|
||||
|
||||
type PluginHookSubagentRequester = {
|
||||
channel?: string;
|
||||
accountId?: string;
|
||||
to?: string;
|
||||
threadId?: string | number;
|
||||
/** Native source channel/conversation id, when distinct from the routable target. */
|
||||
channelId?: string | number;
|
||||
/** Native source message that initiated the parent run, when available. */
|
||||
messageId?: string | number;
|
||||
};
|
||||
|
||||
type PluginHookSubagentSpawnBase = {
|
||||
childSessionKey: string;
|
||||
agentId: string;
|
||||
label?: string;
|
||||
mode: "run" | "session";
|
||||
requester?: {
|
||||
channel?: string;
|
||||
accountId?: string;
|
||||
to?: string;
|
||||
threadId?: string | number;
|
||||
};
|
||||
requester?: PluginHookSubagentRequester;
|
||||
threadRequested: boolean;
|
||||
};
|
||||
|
||||
@@ -835,6 +843,22 @@ export type PluginHookSubagentSpawnedEvent = PluginHookSubagentSpawnBase & {
|
||||
resolvedProvider?: string;
|
||||
};
|
||||
|
||||
/** Portable channel presentation signal for one background child run. */
|
||||
export type PluginHookSubagentProgressEvent =
|
||||
| {
|
||||
phase: "started";
|
||||
runId: string;
|
||||
childSessionKey: string;
|
||||
requester?: PluginHookSubagentRequester;
|
||||
}
|
||||
| {
|
||||
phase: "ended";
|
||||
runId: string;
|
||||
childSessionKey: string;
|
||||
outcome: "ok" | "error" | "timeout" | "killed" | "unknown";
|
||||
requester?: PluginHookSubagentRequester;
|
||||
};
|
||||
|
||||
export type PluginHookSubagentEndedEvent = {
|
||||
targetSessionKey: string;
|
||||
targetKind: PluginHookSubagentTargetKind;
|
||||
@@ -1246,6 +1270,10 @@ export type PluginHookHandlerMap = {
|
||||
event: PluginHookSubagentSpawnedEvent,
|
||||
ctx: PluginHookSubagentContext,
|
||||
) => Promise<void> | void;
|
||||
subagent_progress: (
|
||||
event: PluginHookSubagentProgressEvent,
|
||||
ctx: PluginHookSubagentContext,
|
||||
) => Promise<void> | void;
|
||||
subagent_ended: (
|
||||
event: PluginHookSubagentEndedEvent,
|
||||
ctx: PluginHookSubagentContext,
|
||||
|
||||
+15
-1
@@ -80,6 +80,7 @@ import type {
|
||||
PluginHookSubagentSpawningEvent,
|
||||
PluginHookSubagentSpawningResult,
|
||||
PluginHookSubagentEndedEvent,
|
||||
PluginHookSubagentProgressEvent,
|
||||
PluginHookSubagentSpawnedEvent,
|
||||
PluginHookToolContext,
|
||||
PluginHookToolResultPersistContext,
|
||||
@@ -1448,6 +1449,14 @@ export function createHookRunner(
|
||||
return runVoidHook("subagent_spawned", event, ctx);
|
||||
}
|
||||
|
||||
/** Run portable subagent progress presentation hooks. */
|
||||
async function runSubagentProgress(
|
||||
event: PluginHookSubagentProgressEvent,
|
||||
ctx: PluginHookSubagentContext,
|
||||
): Promise<void> {
|
||||
return runVoidHook("subagent_progress", event, ctx);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run subagent_ended hook.
|
||||
* Runs in parallel (fire-and-forget).
|
||||
@@ -1621,6 +1630,7 @@ export function createHookRunner(
|
||||
runSubagentSpawning,
|
||||
runSubagentDeliveryTarget,
|
||||
runSubagentSpawned,
|
||||
runSubagentProgress,
|
||||
runSubagentEnded,
|
||||
// Gateway hooks
|
||||
runGatewayStart,
|
||||
@@ -1641,6 +1651,10 @@ export type HookRunner = ReturnType<typeof createHookRunner>;
|
||||
|
||||
export type SubagentLifecycleHookRunner = Pick<
|
||||
HookRunner,
|
||||
"hasHooks" | "runSubagentSpawning" | "runSubagentSpawned" | "runSubagentEnded"
|
||||
| "hasHooks"
|
||||
| "runSubagentSpawning"
|
||||
| "runSubagentSpawned"
|
||||
| "runSubagentProgress"
|
||||
| "runSubagentEnded"
|
||||
>;
|
||||
/* oxlint-disable max-lines -- TODO: split this grandfathered oversized file. */
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Test: subagent_spawning, subagent_delivery_target, subagent_spawned & subagent_ended hook wiring
|
||||
* Test: subagent spawning, routing, progress, and terminal hook wiring.
|
||||
*/
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { addStaticTestHooks, createHookRunnerWithRegistry } from "./hooks.test-fixtures.js";
|
||||
@@ -22,6 +22,7 @@ describe("subagent hook runner methods", () => {
|
||||
hookName:
|
||||
| "subagent_spawning"
|
||||
| "subagent_spawned"
|
||||
| "subagent_progress"
|
||||
| "subagent_delivery_target"
|
||||
| "subagent_ended";
|
||||
event: Record<string, unknown>;
|
||||
@@ -38,9 +39,11 @@ describe("subagent hook runner methods", () => {
|
||||
? await runner.runSubagentSpawning(params.event as never, params.ctx as never)
|
||||
: params.hookName === "subagent_spawned"
|
||||
? await runner.runSubagentSpawned(params.event as never, params.ctx as never)
|
||||
: params.hookName === "subagent_delivery_target"
|
||||
? await runner.runSubagentDeliveryTarget(params.event as never, params.ctx as never)
|
||||
: await runner.runSubagentEnded(params.event as never, params.ctx as never);
|
||||
: params.hookName === "subagent_progress"
|
||||
? await runner.runSubagentProgress(params.event as never, params.ctx as never)
|
||||
: params.hookName === "subagent_delivery_target"
|
||||
? await runner.runSubagentDeliveryTarget(params.event as never, params.ctx as never)
|
||||
: await runner.runSubagentEnded(params.event as never, params.ctx as never);
|
||||
|
||||
expect(handler).toHaveBeenCalledWith(params.event, params.ctx);
|
||||
return result;
|
||||
@@ -81,6 +84,18 @@ describe("subagent hook runner methods", () => {
|
||||
},
|
||||
ctx: baseSubagentCtx,
|
||||
},
|
||||
{
|
||||
name: "runSubagentProgress invokes registered subagent_progress hooks",
|
||||
hookName: "subagent_progress" as const,
|
||||
methodName: "runSubagentProgress" as const,
|
||||
event: {
|
||||
phase: "started" as const,
|
||||
runId: "run-1",
|
||||
childSessionKey: "agent:main:subagent:child",
|
||||
requester: { ...baseRequester, messageId: "message-1" },
|
||||
},
|
||||
ctx: baseSubagentCtx,
|
||||
},
|
||||
{
|
||||
name: "runSubagentDeliveryTarget invokes registered subagent_delivery_target hooks",
|
||||
hookName: "subagent_delivery_target" as const,
|
||||
@@ -160,6 +175,7 @@ describe("subagent hook runner methods", () => {
|
||||
expect(runner.hasHooks("subagent_spawning")).toBe(true);
|
||||
expect(runner.hasHooks("subagent_delivery_target")).toBe(true);
|
||||
expect(runner.hasHooks("subagent_spawned")).toBe(false);
|
||||
expect(runner.hasHooks("subagent_progress")).toBe(false);
|
||||
expect(runner.hasHooks("subagent_ended")).toBe(false);
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user