feat: add sessionKey to dispatch options and related tools for subagent tracking
This commit is contained in:
@@ -4,8 +4,7 @@
|
|||||||
* Handles: session lookup, spawn/reuse via Gateway RPC, task dispatch via CLI,
|
* Handles: session lookup, spawn/reuse via Gateway RPC, task dispatch via CLI,
|
||||||
* state update (activateWorker), and audit logging.
|
* state update (activateWorker), and audit logging.
|
||||||
*/
|
*/
|
||||||
import { execFile } from "node:child_process";
|
import { execFile, spawn } from "node:child_process";
|
||||||
import { randomUUID } from "node:crypto";
|
|
||||||
import fs from "node:fs/promises";
|
import fs from "node:fs/promises";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { promisify } from "node:util";
|
import { promisify } from "node:util";
|
||||||
@@ -40,6 +39,8 @@ export type DispatchOpts = {
|
|||||||
transitionLabel: (issueId: number, from: string, to: string) => Promise<void>;
|
transitionLabel: (issueId: number, from: string, to: string) => Promise<void>;
|
||||||
/** Plugin config for model resolution */
|
/** Plugin config for model resolution */
|
||||||
pluginConfig?: Record<string, unknown>;
|
pluginConfig?: Record<string, unknown>;
|
||||||
|
/** Orchestrator's session key (used as spawnedBy for subagent tracking) */
|
||||||
|
sessionKey?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type DispatchResult = {
|
export type DispatchResult = {
|
||||||
@@ -201,7 +202,7 @@ export async function dispatchTask(
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
if (sessionAction === "spawn") {
|
if (sessionAction === "spawn") {
|
||||||
sessionKey = `agent:${agentId ?? "unknown"}:subagent:${randomUUID()}`;
|
sessionKey = `agent:${agentId ?? "unknown"}:subagent:${project.name}-${role}-${modelAlias}`;
|
||||||
await execFileAsync(
|
await execFileAsync(
|
||||||
"openclaw",
|
"openclaw",
|
||||||
[
|
[
|
||||||
@@ -215,21 +216,38 @@ export async function dispatchTask(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
await execFileAsync(
|
// Dispatch via `gateway call agent --expect-final` as a detached background process.
|
||||||
|
// Without --expect-final the gateway accepts but never processes the request.
|
||||||
|
// Running with --expect-final in a detached process ensures the agent runs
|
||||||
|
// while task_pickup returns immediately.
|
||||||
|
// Using the gateway RPC (not `openclaw agent` CLI) lets us set lane, spawnedBy,
|
||||||
|
// and deliver — matching the official sessions_spawn internals.
|
||||||
|
const orchestratorSessionKey = opts.sessionKey;
|
||||||
|
const gatewayParams = JSON.stringify({
|
||||||
|
idempotencyKey: `devclaw-${project.name}-${issueId}-${role}-${Date.now()}`,
|
||||||
|
agentId: agentId ?? "devclaw",
|
||||||
|
sessionKey: sessionKey!,
|
||||||
|
message: taskMessage,
|
||||||
|
deliver: false,
|
||||||
|
lane: "subagent",
|
||||||
|
...(orchestratorSessionKey
|
||||||
|
? { spawnedBy: orchestratorSessionKey }
|
||||||
|
: {}),
|
||||||
|
});
|
||||||
|
const child = spawn(
|
||||||
"openclaw",
|
"openclaw",
|
||||||
[
|
[
|
||||||
"gateway",
|
"gateway",
|
||||||
"call",
|
"call",
|
||||||
"agent",
|
"agent",
|
||||||
"--params",
|
"--params",
|
||||||
JSON.stringify({
|
gatewayParams,
|
||||||
idempotencyKey: randomUUID(),
|
"--expect-final",
|
||||||
sessionId: sessionKey!,
|
"--json",
|
||||||
message: taskMessage,
|
|
||||||
}),
|
|
||||||
],
|
],
|
||||||
{ timeout: 30_000 },
|
{ detached: true, stdio: "ignore" },
|
||||||
);
|
);
|
||||||
|
child.unref();
|
||||||
|
|
||||||
dispatched = true;
|
dispatched = true;
|
||||||
|
|
||||||
|
|||||||
@@ -510,6 +510,7 @@ export function createHeartbeatTickTool(api: OpenClawPluginApi) {
|
|||||||
transitionLabel: (id, from, to) =>
|
transitionLabel: (id, from, to) =>
|
||||||
provider.transitionLabel(id, from as StateLabel, to as StateLabel),
|
provider.transitionLabel(id, from as StateLabel, to as StateLabel),
|
||||||
pluginConfig,
|
pluginConfig,
|
||||||
|
sessionKey: ctx.sessionKey,
|
||||||
});
|
});
|
||||||
|
|
||||||
result.pickups.push({
|
result.pickups.push({
|
||||||
|
|||||||
@@ -165,6 +165,7 @@ export function createTaskCompleteTool(api: OpenClawPluginApi) {
|
|||||||
to as StateLabel,
|
to as StateLabel,
|
||||||
),
|
),
|
||||||
pluginConfig,
|
pluginConfig,
|
||||||
|
sessionKey: ctx.sessionKey,
|
||||||
});
|
});
|
||||||
output.autoChain = {
|
output.autoChain = {
|
||||||
dispatched: true,
|
dispatched: true,
|
||||||
@@ -239,6 +240,7 @@ export function createTaskCompleteTool(api: OpenClawPluginApi) {
|
|||||||
to as StateLabel,
|
to as StateLabel,
|
||||||
),
|
),
|
||||||
pluginConfig,
|
pluginConfig,
|
||||||
|
sessionKey: ctx.sessionKey,
|
||||||
});
|
});
|
||||||
output.autoChain = {
|
output.autoChain = {
|
||||||
dispatched: true,
|
dispatched: true,
|
||||||
|
|||||||
@@ -311,6 +311,7 @@ export function createTaskPickupTool(api: OpenClawPluginApi) {
|
|||||||
transitionLabel: (id, from, to) =>
|
transitionLabel: (id, from, to) =>
|
||||||
provider.transitionLabel(id, from as StateLabel, to as StateLabel),
|
provider.transitionLabel(id, from as StateLabel, to as StateLabel),
|
||||||
pluginConfig,
|
pluginConfig,
|
||||||
|
sessionKey: ctx.sessionKey,
|
||||||
});
|
});
|
||||||
|
|
||||||
// 8b. Explicitly update worker state in projects.json
|
// 8b. Explicitly update worker state in projects.json
|
||||||
|
|||||||
Reference in New Issue
Block a user