mirror of https://github.com/openclaw/openclaw.git
Revert "refactor: move tasks behind plugin-sdk seam"
This reverts commit da6e9bb76f.
This commit is contained in:
parent
6f74a572d9
commit
759d37635d
|
|
@ -1 +0,0 @@
|
|||
export * from "./runtime-api.js";
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
import { describe, expect, it, vi } from "vitest";
|
||||
import { createTestPluginApi } from "../../test/helpers/plugins/plugin-api.js";
|
||||
import { createPluginRuntimeMock } from "../../test/helpers/plugins/plugin-runtime-mock.js";
|
||||
import tasksPlugin from "./index.js";
|
||||
|
||||
describe("tasks plugin", () => {
|
||||
it("registers the default operations runtime, maintenance service, and CLI", () => {
|
||||
const registerOperationsRuntime = vi.fn();
|
||||
const registerService = vi.fn();
|
||||
const registerCli = vi.fn();
|
||||
|
||||
tasksPlugin.register(
|
||||
createTestPluginApi({
|
||||
id: "tasks",
|
||||
name: "Tasks",
|
||||
source: "test",
|
||||
config: {},
|
||||
runtime: createPluginRuntimeMock(),
|
||||
registerOperationsRuntime,
|
||||
registerService,
|
||||
registerCli,
|
||||
}),
|
||||
);
|
||||
|
||||
expect(registerOperationsRuntime).toHaveBeenCalledTimes(1);
|
||||
expect(registerService).toHaveBeenCalledTimes(1);
|
||||
expect(registerCli).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
|
||||
import {
|
||||
createDefaultOperationsMaintenanceService,
|
||||
defaultOperationsRuntime,
|
||||
} from "./runtime-api.js";
|
||||
import { registerTasksCli } from "./src/cli.js";
|
||||
|
||||
export default definePluginEntry({
|
||||
id: "tasks",
|
||||
name: "Tasks",
|
||||
description: "Durable task inspection and maintenance CLI",
|
||||
register(api) {
|
||||
api.registerOperationsRuntime(defaultOperationsRuntime);
|
||||
api.registerService(createDefaultOperationsMaintenanceService());
|
||||
api.registerCli(
|
||||
({ program }) => {
|
||||
registerTasksCli(program, {
|
||||
config: api.config,
|
||||
operations: api.runtime.operations,
|
||||
});
|
||||
},
|
||||
{
|
||||
descriptors: [
|
||||
{
|
||||
name: "tasks",
|
||||
description: "Inspect durable background task state",
|
||||
hasSubcommands: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
);
|
||||
},
|
||||
});
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
{
|
||||
"id": "tasks",
|
||||
"enabledByDefault": true,
|
||||
"configSchema": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
{
|
||||
"name": "@openclaw/tasks",
|
||||
"version": "2026.3.31",
|
||||
"private": true,
|
||||
"description": "OpenClaw durable tasks plugin",
|
||||
"type": "module",
|
||||
"openclaw": {
|
||||
"extensions": [
|
||||
"./index.ts"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
export {
|
||||
createDefaultOperationsMaintenanceService,
|
||||
defaultOperationsRuntime,
|
||||
} from "openclaw/plugin-sdk/tasks";
|
||||
|
|
@ -1,371 +0,0 @@
|
|||
import type {
|
||||
PluginOperationAuditFinding,
|
||||
PluginOperationRecord,
|
||||
PluginOperationsRuntime,
|
||||
} from "openclaw/plugin-sdk/plugin-entry";
|
||||
import { createLoggerBackedRuntime, type OutputRuntimeEnv } from "openclaw/plugin-sdk/runtime";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
runTasksAudit,
|
||||
runTasksCancel,
|
||||
runTasksList,
|
||||
runTasksMaintenance,
|
||||
runTasksNotify,
|
||||
runTasksShow,
|
||||
} from "./cli.runtime.js";
|
||||
|
||||
function createRuntimeCapture() {
|
||||
const logs: string[] = [];
|
||||
const errors: string[] = [];
|
||||
const runtime = createLoggerBackedRuntime({
|
||||
logger: {
|
||||
info(message) {
|
||||
logs.push(message);
|
||||
},
|
||||
error(message) {
|
||||
errors.push(message);
|
||||
},
|
||||
},
|
||||
exitError(code) {
|
||||
return new Error(`exit ${code}`);
|
||||
},
|
||||
}) as OutputRuntimeEnv;
|
||||
return { runtime, logs, errors };
|
||||
}
|
||||
|
||||
function createOperationsMock(): PluginOperationsRuntime {
|
||||
return {
|
||||
dispatch: vi.fn().mockResolvedValue({
|
||||
matched: false,
|
||||
record: null,
|
||||
}),
|
||||
getById: vi.fn().mockResolvedValue(null),
|
||||
findByRunId: vi.fn().mockResolvedValue(null),
|
||||
list: vi.fn().mockResolvedValue([]),
|
||||
summarize: vi.fn().mockResolvedValue({
|
||||
total: 0,
|
||||
active: 0,
|
||||
terminal: 0,
|
||||
failures: 0,
|
||||
byNamespace: {},
|
||||
byKind: {},
|
||||
byStatus: {},
|
||||
}),
|
||||
audit: vi.fn().mockResolvedValue([]),
|
||||
maintenance: vi.fn().mockResolvedValue({
|
||||
reconciled: 0,
|
||||
cleanupStamped: 0,
|
||||
pruned: 0,
|
||||
}),
|
||||
cancel: vi.fn().mockResolvedValue({
|
||||
found: false,
|
||||
cancelled: false,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
const taskFixture: PluginOperationRecord = {
|
||||
operationId: "task-12345678",
|
||||
namespace: "tasks",
|
||||
kind: "acp",
|
||||
status: "running",
|
||||
sourceId: "run-12345678",
|
||||
requesterSessionKey: "agent:main:main",
|
||||
childSessionKey: "agent:codex:acp:child",
|
||||
runId: "run-12345678",
|
||||
title: "Task title",
|
||||
description: "Create a file",
|
||||
createdAt: Date.parse("2026-03-29T10:00:00.000Z"),
|
||||
updatedAt: Date.parse("2026-03-29T10:00:10.000Z"),
|
||||
progressSummary: "No output for 60s. It may be waiting for input.",
|
||||
metadata: {
|
||||
deliveryStatus: "pending",
|
||||
notifyPolicy: "state_changes",
|
||||
},
|
||||
};
|
||||
|
||||
describe("tasks CLI runtime", () => {
|
||||
let operations: PluginOperationsRuntime;
|
||||
let logs: string[];
|
||||
let errors: string[];
|
||||
let runtime: OutputRuntimeEnv;
|
||||
let config: import("openclaw/plugin-sdk/plugin-entry").OpenClawConfig;
|
||||
|
||||
beforeEach(() => {
|
||||
operations = createOperationsMock();
|
||||
({ runtime, logs, errors } = createRuntimeCapture());
|
||||
config = {};
|
||||
});
|
||||
|
||||
it("lists task rows with progress summary fallback", async () => {
|
||||
vi.mocked(operations.list).mockResolvedValue([taskFixture]);
|
||||
|
||||
await runTasksList(
|
||||
{
|
||||
runtime: "acp",
|
||||
status: "running",
|
||||
},
|
||||
{
|
||||
config,
|
||||
operations,
|
||||
},
|
||||
runtime,
|
||||
);
|
||||
|
||||
expect(logs[0]).toContain("Background tasks: 1");
|
||||
expect(logs[1]).toContain("Task pressure: 0 queued · 1 running · 0 issues");
|
||||
expect(logs.join("\n")).toContain("No output for 60s. It may be waiting for input.");
|
||||
});
|
||||
|
||||
it("shows detailed task fields including notify and recent events", async () => {
|
||||
vi.mocked(operations.findByRunId).mockResolvedValue(taskFixture);
|
||||
|
||||
await runTasksShow(
|
||||
{ lookup: "run-12345678" },
|
||||
{
|
||||
config: {},
|
||||
operations,
|
||||
},
|
||||
runtime,
|
||||
);
|
||||
|
||||
expect(logs.join("\n")).toContain("notify: state_changes");
|
||||
expect(logs.join("\n")).toContain(
|
||||
"progressSummary: No output for 60s. It may be waiting for input.",
|
||||
);
|
||||
});
|
||||
|
||||
it("updates notify policy for an existing task", async () => {
|
||||
vi.mocked(operations.findByRunId).mockResolvedValue(taskFixture);
|
||||
vi.mocked(operations.dispatch).mockResolvedValue({
|
||||
matched: true,
|
||||
record: {
|
||||
...taskFixture,
|
||||
metadata: {
|
||||
...taskFixture.metadata,
|
||||
notifyPolicy: "silent",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await runTasksNotify(
|
||||
{ lookup: "run-12345678", notify: "silent" },
|
||||
{
|
||||
config: {},
|
||||
operations,
|
||||
},
|
||||
runtime,
|
||||
);
|
||||
|
||||
expect(operations.dispatch).toHaveBeenCalledWith({
|
||||
type: "patch",
|
||||
operationId: "task-12345678",
|
||||
at: expect.any(Number),
|
||||
metadataPatch: {
|
||||
notifyPolicy: "silent",
|
||||
},
|
||||
});
|
||||
expect(logs[0]).toContain("Updated task-12345678 notify policy to silent.");
|
||||
});
|
||||
|
||||
it("cancels a running task and reports the updated runtime", async () => {
|
||||
vi.mocked(operations.findByRunId).mockResolvedValue(taskFixture);
|
||||
vi.mocked(operations.cancel).mockResolvedValue({
|
||||
found: true,
|
||||
cancelled: true,
|
||||
record: {
|
||||
...taskFixture,
|
||||
status: "cancelled",
|
||||
},
|
||||
});
|
||||
vi.mocked(operations.getById).mockResolvedValue({
|
||||
...taskFixture,
|
||||
status: "cancelled",
|
||||
});
|
||||
|
||||
await runTasksCancel(
|
||||
{ lookup: "run-12345678" },
|
||||
{
|
||||
config,
|
||||
operations,
|
||||
},
|
||||
runtime,
|
||||
);
|
||||
|
||||
expect(operations.cancel).toHaveBeenCalledWith({
|
||||
cfg: config,
|
||||
operationId: "task-12345678",
|
||||
});
|
||||
expect(logs[0]).toContain("Cancelled task-12345678 (acp) run run-12345678.");
|
||||
expect(errors).toEqual([]);
|
||||
});
|
||||
|
||||
it("shows task audit findings with filters", async () => {
|
||||
const findings: PluginOperationAuditFinding[] = [
|
||||
{
|
||||
severity: "error",
|
||||
code: "stale_running",
|
||||
operation: taskFixture,
|
||||
ageMs: 45 * 60_000,
|
||||
detail: "running task appears stuck",
|
||||
},
|
||||
{
|
||||
severity: "warn",
|
||||
code: "delivery_failed",
|
||||
operation: {
|
||||
...taskFixture,
|
||||
operationId: "task-87654321",
|
||||
status: "failed",
|
||||
},
|
||||
ageMs: 10 * 60_000,
|
||||
detail: "terminal update delivery failed",
|
||||
},
|
||||
];
|
||||
vi.mocked(operations.audit)
|
||||
.mockResolvedValueOnce(findings)
|
||||
.mockResolvedValueOnce([findings[0]!]);
|
||||
|
||||
await runTasksAudit(
|
||||
{ severity: "error", code: "stale_running", limit: 1 },
|
||||
{
|
||||
config: {},
|
||||
operations,
|
||||
},
|
||||
runtime,
|
||||
);
|
||||
|
||||
expect(logs[0]).toContain("Task audit: 2 findings · 1 errors · 1 warnings");
|
||||
expect(logs[1]).toContain("Showing 1 matching findings.");
|
||||
expect(logs.join("\n")).toContain("stale_running");
|
||||
expect(logs.join("\n")).toContain("running task appears stuck");
|
||||
expect(logs.join("\n")).not.toContain("delivery_failed");
|
||||
});
|
||||
|
||||
it("previews task maintenance without applying changes", async () => {
|
||||
vi.mocked(operations.audit).mockResolvedValue([
|
||||
{
|
||||
severity: "error",
|
||||
code: "stale_running",
|
||||
operation: taskFixture,
|
||||
detail: "running task appears stuck",
|
||||
},
|
||||
{
|
||||
severity: "warn",
|
||||
code: "lost",
|
||||
operation: {
|
||||
...taskFixture,
|
||||
operationId: "task-2",
|
||||
status: "lost",
|
||||
},
|
||||
detail: "backing session missing",
|
||||
},
|
||||
]);
|
||||
vi.mocked(operations.maintenance).mockResolvedValue({
|
||||
reconciled: 2,
|
||||
cleanupStamped: 1,
|
||||
pruned: 3,
|
||||
});
|
||||
vi.mocked(operations.summarize).mockResolvedValue({
|
||||
total: 5,
|
||||
active: 2,
|
||||
terminal: 3,
|
||||
failures: 1,
|
||||
byNamespace: { tasks: 5 },
|
||||
byKind: { acp: 1, cron: 2, subagent: 1, cli: 1 },
|
||||
byStatus: {
|
||||
queued: 1,
|
||||
running: 1,
|
||||
succeeded: 1,
|
||||
lost: 1,
|
||||
failed: 1,
|
||||
},
|
||||
});
|
||||
|
||||
await runTasksMaintenance(
|
||||
{},
|
||||
{
|
||||
config: {},
|
||||
operations,
|
||||
},
|
||||
runtime,
|
||||
);
|
||||
|
||||
expect(logs[0]).toContain(
|
||||
"Task maintenance (preview): 2 reconcile · 1 cleanup stamp · 3 prune",
|
||||
);
|
||||
expect(logs[1]).toContain(
|
||||
"Task health: 1 queued · 1 running · 1 audit errors · 1 audit warnings",
|
||||
);
|
||||
expect(logs[2]).toContain("Dry run only.");
|
||||
});
|
||||
|
||||
it("shows before and after audit health when applying maintenance", async () => {
|
||||
vi.mocked(operations.audit)
|
||||
.mockResolvedValueOnce([
|
||||
{
|
||||
severity: "error",
|
||||
code: "stale_running",
|
||||
operation: taskFixture,
|
||||
detail: "running task appears stuck",
|
||||
},
|
||||
{
|
||||
severity: "warn",
|
||||
code: "missing_cleanup",
|
||||
operation: {
|
||||
...taskFixture,
|
||||
operationId: "task-2",
|
||||
status: "succeeded",
|
||||
},
|
||||
detail: "missing cleanupAfter",
|
||||
},
|
||||
])
|
||||
.mockResolvedValueOnce([
|
||||
{
|
||||
severity: "warn",
|
||||
code: "lost",
|
||||
operation: {
|
||||
...taskFixture,
|
||||
operationId: "task-2",
|
||||
status: "lost",
|
||||
},
|
||||
detail: "backing session missing",
|
||||
},
|
||||
]);
|
||||
vi.mocked(operations.maintenance).mockResolvedValue({
|
||||
reconciled: 2,
|
||||
cleanupStamped: 1,
|
||||
pruned: 3,
|
||||
});
|
||||
vi.mocked(operations.summarize).mockResolvedValue({
|
||||
total: 4,
|
||||
active: 2,
|
||||
terminal: 2,
|
||||
failures: 1,
|
||||
byNamespace: { tasks: 4 },
|
||||
byKind: { acp: 1, cron: 2, subagent: 1 },
|
||||
byStatus: {
|
||||
queued: 1,
|
||||
running: 1,
|
||||
succeeded: 1,
|
||||
lost: 1,
|
||||
},
|
||||
});
|
||||
|
||||
await runTasksMaintenance(
|
||||
{ apply: true },
|
||||
{
|
||||
config: {},
|
||||
operations,
|
||||
},
|
||||
runtime,
|
||||
);
|
||||
|
||||
expect(logs[0]).toContain(
|
||||
"Task maintenance (applied): 2 reconcile · 1 cleanup stamp · 3 prune",
|
||||
);
|
||||
expect(logs[1]).toContain(
|
||||
"Task health after apply: 1 queued · 1 running · 0 audit errors · 1 audit warnings",
|
||||
);
|
||||
expect(logs[2]).toContain("Task health before apply: 1 audit errors · 1 audit warnings");
|
||||
});
|
||||
});
|
||||
|
|
@ -1,464 +0,0 @@
|
|||
import type {
|
||||
OpenClawConfig,
|
||||
PluginOperationAuditFinding,
|
||||
PluginOperationRecord,
|
||||
PluginOperationsRuntime,
|
||||
} from "openclaw/plugin-sdk/plugin-entry";
|
||||
import { info, type RuntimeEnv } from "openclaw/plugin-sdk/runtime";
|
||||
|
||||
type TasksCliDeps = {
|
||||
config: OpenClawConfig;
|
||||
operations: PluginOperationsRuntime;
|
||||
};
|
||||
|
||||
type TaskNotifyPolicy = "done_only" | "state_changes" | "silent";
|
||||
|
||||
const KIND_PAD = 8;
|
||||
const STATUS_PAD = 10;
|
||||
const DELIVERY_PAD = 14;
|
||||
const ID_PAD = 10;
|
||||
const RUN_PAD = 10;
|
||||
|
||||
function truncate(value: string, maxChars: number) {
|
||||
if (value.length <= maxChars) {
|
||||
return value;
|
||||
}
|
||||
if (maxChars <= 1) {
|
||||
return value.slice(0, maxChars);
|
||||
}
|
||||
return `${value.slice(0, maxChars - 1)}…`;
|
||||
}
|
||||
|
||||
function shortToken(value: string | undefined, maxChars = ID_PAD): string {
|
||||
const trimmed = value?.trim();
|
||||
if (!trimmed) {
|
||||
return "n/a";
|
||||
}
|
||||
return truncate(trimmed, maxChars);
|
||||
}
|
||||
|
||||
function readStringMetadata(record: PluginOperationRecord, key: string): string | undefined {
|
||||
const value = record.metadata?.[key];
|
||||
return typeof value === "string" && value.trim() ? value : undefined;
|
||||
}
|
||||
|
||||
function readNumberMetadata(record: PluginOperationRecord, key: string): number | undefined {
|
||||
const value = record.metadata?.[key];
|
||||
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
||||
}
|
||||
|
||||
function formatTaskRows(tasks: PluginOperationRecord[]) {
|
||||
const header = [
|
||||
"Task".padEnd(ID_PAD),
|
||||
"Kind".padEnd(KIND_PAD),
|
||||
"Status".padEnd(STATUS_PAD),
|
||||
"Delivery".padEnd(DELIVERY_PAD),
|
||||
"Run".padEnd(RUN_PAD),
|
||||
"Child Session".padEnd(36),
|
||||
"Summary",
|
||||
].join(" ");
|
||||
const lines = [header];
|
||||
for (const task of tasks) {
|
||||
const summary = truncate(
|
||||
task.terminalSummary?.trim() ||
|
||||
task.progressSummary?.trim() ||
|
||||
task.title?.trim() ||
|
||||
task.description.trim(),
|
||||
80,
|
||||
);
|
||||
const line = [
|
||||
shortToken(task.operationId).padEnd(ID_PAD),
|
||||
task.kind.padEnd(KIND_PAD),
|
||||
task.status.padEnd(STATUS_PAD),
|
||||
(readStringMetadata(task, "deliveryStatus") ?? "n/a").padEnd(DELIVERY_PAD),
|
||||
shortToken(task.runId, RUN_PAD).padEnd(RUN_PAD),
|
||||
truncate(task.childSessionKey?.trim() || "n/a", 36).padEnd(36),
|
||||
summary,
|
||||
].join(" ");
|
||||
lines.push(line.trimEnd());
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
|
||||
function formatAgeMs(ageMs: number | undefined): string {
|
||||
if (typeof ageMs !== "number" || ageMs < 1000) {
|
||||
return "fresh";
|
||||
}
|
||||
const totalSeconds = Math.floor(ageMs / 1000);
|
||||
const days = Math.floor(totalSeconds / 86_400);
|
||||
const hours = Math.floor((totalSeconds % 86_400) / 3600);
|
||||
const minutes = Math.floor((totalSeconds % 3600) / 60);
|
||||
if (days > 0) {
|
||||
return `${days}d${hours}h`;
|
||||
}
|
||||
if (hours > 0) {
|
||||
return `${hours}h${minutes}m`;
|
||||
}
|
||||
if (minutes > 0) {
|
||||
return `${minutes}m`;
|
||||
}
|
||||
return `${totalSeconds}s`;
|
||||
}
|
||||
|
||||
function formatAuditRows(findings: PluginOperationAuditFinding[]) {
|
||||
const header = [
|
||||
"Severity".padEnd(8),
|
||||
"Code".padEnd(22),
|
||||
"Task".padEnd(ID_PAD),
|
||||
"Status".padEnd(STATUS_PAD),
|
||||
"Age".padEnd(8),
|
||||
"Detail",
|
||||
].join(" ");
|
||||
const lines = [header];
|
||||
for (const finding of findings) {
|
||||
lines.push(
|
||||
[
|
||||
finding.severity.padEnd(8),
|
||||
finding.code.padEnd(22),
|
||||
shortToken(finding.operation.operationId).padEnd(ID_PAD),
|
||||
finding.operation.status.padEnd(STATUS_PAD),
|
||||
formatAgeMs(finding.ageMs).padEnd(8),
|
||||
truncate(finding.detail, 88),
|
||||
]
|
||||
.join(" ")
|
||||
.trimEnd(),
|
||||
);
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
|
||||
function summarizeAuditFindings(findings: Iterable<PluginOperationAuditFinding>) {
|
||||
const summary = {
|
||||
total: 0,
|
||||
warnings: 0,
|
||||
errors: 0,
|
||||
byCode: {} as Record<string, number>,
|
||||
};
|
||||
for (const finding of findings) {
|
||||
summary.total += 1;
|
||||
summary.byCode[finding.code] = (summary.byCode[finding.code] ?? 0) + 1;
|
||||
if (finding.severity === "error") {
|
||||
summary.errors += 1;
|
||||
continue;
|
||||
}
|
||||
summary.warnings += 1;
|
||||
}
|
||||
return summary;
|
||||
}
|
||||
|
||||
function formatTaskListSummary(tasks: PluginOperationRecord[]) {
|
||||
const queued = tasks.filter((task) => task.status === "queued").length;
|
||||
const running = tasks.filter((task) => task.status === "running").length;
|
||||
const failures = tasks.filter(
|
||||
(task) => task.status === "failed" || task.status === "timed_out" || task.status === "lost",
|
||||
).length;
|
||||
return `${queued} queued · ${running} running · ${failures} issues`;
|
||||
}
|
||||
|
||||
async function resolveTaskLookupToken(
|
||||
operations: PluginOperationsRuntime,
|
||||
lookup: string,
|
||||
): Promise<PluginOperationRecord | null> {
|
||||
const token = lookup.trim();
|
||||
if (!token) {
|
||||
return null;
|
||||
}
|
||||
const byId = await operations.getById(token);
|
||||
if (byId?.namespace === "tasks") {
|
||||
return byId;
|
||||
}
|
||||
const byRunId = await operations.findByRunId(token);
|
||||
if (byRunId?.namespace === "tasks") {
|
||||
return byRunId;
|
||||
}
|
||||
const bySession = await operations.list({
|
||||
namespace: "tasks",
|
||||
sessionKey: token,
|
||||
limit: 1,
|
||||
});
|
||||
return bySession[0] ?? null;
|
||||
}
|
||||
|
||||
export async function runTasksList(
|
||||
opts: { json?: boolean; runtime?: string; status?: string },
|
||||
deps: TasksCliDeps,
|
||||
runtime: RuntimeEnv,
|
||||
) {
|
||||
const tasks = await deps.operations.list({
|
||||
namespace: "tasks",
|
||||
...(opts.runtime ? { kind: opts.runtime.trim() } : {}),
|
||||
...(opts.status ? { status: opts.status.trim() } : {}),
|
||||
});
|
||||
|
||||
if (opts.json) {
|
||||
runtime.log(
|
||||
JSON.stringify(
|
||||
{
|
||||
count: tasks.length,
|
||||
runtime: opts.runtime ?? null,
|
||||
status: opts.status ?? null,
|
||||
tasks,
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
runtime.log(info(`Background tasks: ${tasks.length}`));
|
||||
runtime.log(info(`Task pressure: ${formatTaskListSummary(tasks)}`));
|
||||
if (opts.runtime) {
|
||||
runtime.log(info(`Runtime filter: ${opts.runtime}`));
|
||||
}
|
||||
if (opts.status) {
|
||||
runtime.log(info(`Status filter: ${opts.status}`));
|
||||
}
|
||||
if (tasks.length === 0) {
|
||||
runtime.log("No background tasks found.");
|
||||
return;
|
||||
}
|
||||
for (const line of formatTaskRows(tasks)) {
|
||||
runtime.log(line);
|
||||
}
|
||||
}
|
||||
|
||||
export async function runTasksShow(
|
||||
opts: { json?: boolean; lookup: string },
|
||||
deps: TasksCliDeps,
|
||||
runtime: RuntimeEnv,
|
||||
) {
|
||||
const task = await resolveTaskLookupToken(deps.operations, opts.lookup);
|
||||
if (!task) {
|
||||
runtime.error(`Task not found: ${opts.lookup}`);
|
||||
runtime.exit(1);
|
||||
return;
|
||||
}
|
||||
|
||||
if (opts.json) {
|
||||
runtime.log(JSON.stringify(task, null, 2));
|
||||
return;
|
||||
}
|
||||
|
||||
const lines = [
|
||||
"Background task:",
|
||||
`taskId: ${task.operationId}`,
|
||||
`kind: ${task.kind}`,
|
||||
`sourceId: ${task.sourceId ?? "n/a"}`,
|
||||
`status: ${task.status}`,
|
||||
`result: ${readStringMetadata(task, "terminalOutcome") ?? "n/a"}`,
|
||||
`delivery: ${readStringMetadata(task, "deliveryStatus") ?? "n/a"}`,
|
||||
`notify: ${readStringMetadata(task, "notifyPolicy") ?? "n/a"}`,
|
||||
`requesterSessionKey: ${task.requesterSessionKey ?? "n/a"}`,
|
||||
`childSessionKey: ${task.childSessionKey ?? "n/a"}`,
|
||||
`parentTaskId: ${task.parentOperationId ?? "n/a"}`,
|
||||
`agentId: ${task.agentId ?? "n/a"}`,
|
||||
`runId: ${task.runId ?? "n/a"}`,
|
||||
`label: ${task.title ?? "n/a"}`,
|
||||
`task: ${task.description}`,
|
||||
`createdAt: ${new Date(task.createdAt).toISOString()}`,
|
||||
`startedAt: ${task.startedAt ? new Date(task.startedAt).toISOString() : "n/a"}`,
|
||||
`endedAt: ${task.endedAt ? new Date(task.endedAt).toISOString() : "n/a"}`,
|
||||
`lastEventAt: ${new Date(task.updatedAt).toISOString()}`,
|
||||
`cleanupAfter: ${(() => {
|
||||
const cleanupAfter = readNumberMetadata(task, "cleanupAfter");
|
||||
return cleanupAfter ? new Date(cleanupAfter).toISOString() : "n/a";
|
||||
})()}`,
|
||||
...(task.error ? [`error: ${task.error}`] : []),
|
||||
...(task.progressSummary ? [`progressSummary: ${task.progressSummary}`] : []),
|
||||
...(task.terminalSummary ? [`terminalSummary: ${task.terminalSummary}`] : []),
|
||||
];
|
||||
for (const line of lines) {
|
||||
runtime.log(line);
|
||||
}
|
||||
}
|
||||
|
||||
export async function runTasksNotify(
|
||||
opts: { lookup: string; notify: TaskNotifyPolicy },
|
||||
deps: TasksCliDeps,
|
||||
runtime: RuntimeEnv,
|
||||
) {
|
||||
const task = await resolveTaskLookupToken(deps.operations, opts.lookup);
|
||||
if (!task) {
|
||||
runtime.error(`Task not found: ${opts.lookup}`);
|
||||
runtime.exit(1);
|
||||
return;
|
||||
}
|
||||
const updated = await deps.operations.dispatch({
|
||||
type: "patch",
|
||||
operationId: task.operationId,
|
||||
at: Date.now(),
|
||||
metadataPatch: {
|
||||
notifyPolicy: opts.notify,
|
||||
},
|
||||
});
|
||||
if (!updated.matched || !updated.record) {
|
||||
runtime.error(`Task not found: ${opts.lookup}`);
|
||||
runtime.exit(1);
|
||||
return;
|
||||
}
|
||||
runtime.log(`Updated ${updated.record.operationId} notify policy to ${opts.notify}.`);
|
||||
}
|
||||
|
||||
export async function runTasksCancel(
|
||||
opts: { lookup: string },
|
||||
deps: TasksCliDeps,
|
||||
runtime: RuntimeEnv,
|
||||
) {
|
||||
const task = await resolveTaskLookupToken(deps.operations, opts.lookup);
|
||||
if (!task) {
|
||||
runtime.error(`Task not found: ${opts.lookup}`);
|
||||
runtime.exit(1);
|
||||
return;
|
||||
}
|
||||
const result = await deps.operations.cancel({
|
||||
cfg: deps.config,
|
||||
operationId: task.operationId,
|
||||
});
|
||||
if (!result.found) {
|
||||
runtime.error(result.reason ?? `Task not found: ${opts.lookup}`);
|
||||
runtime.exit(1);
|
||||
return;
|
||||
}
|
||||
if (!result.cancelled) {
|
||||
runtime.error(result.reason ?? `Could not cancel task: ${opts.lookup}`);
|
||||
runtime.exit(1);
|
||||
return;
|
||||
}
|
||||
const updated = await deps.operations.getById(task.operationId);
|
||||
runtime.log(
|
||||
`Cancelled ${updated?.operationId ?? task.operationId} (${updated?.kind ?? task.kind})${updated?.runId ? ` run ${updated.runId}` : ""}.`,
|
||||
);
|
||||
}
|
||||
|
||||
export async function runTasksAudit(
|
||||
opts: {
|
||||
json?: boolean;
|
||||
severity?: "warn" | "error";
|
||||
code?: string;
|
||||
limit?: number;
|
||||
},
|
||||
deps: TasksCliDeps,
|
||||
runtime: RuntimeEnv,
|
||||
) {
|
||||
const allFindings = await deps.operations.audit({
|
||||
namespace: "tasks",
|
||||
});
|
||||
const findings = await deps.operations.audit({
|
||||
namespace: "tasks",
|
||||
...(opts.severity ? { severity: opts.severity } : {}),
|
||||
...(opts.code ? { code: opts.code.trim() } : {}),
|
||||
});
|
||||
const displayed =
|
||||
typeof opts.limit === "number" && opts.limit > 0 ? findings.slice(0, opts.limit) : findings;
|
||||
const summary = summarizeAuditFindings(allFindings);
|
||||
|
||||
if (opts.json) {
|
||||
runtime.log(
|
||||
JSON.stringify(
|
||||
{
|
||||
count: allFindings.length,
|
||||
filteredCount: findings.length,
|
||||
displayed: displayed.length,
|
||||
filters: {
|
||||
severity: opts.severity ?? null,
|
||||
code: opts.code ?? null,
|
||||
limit: opts.limit ?? null,
|
||||
},
|
||||
summary,
|
||||
findings: displayed,
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
runtime.log(
|
||||
info(
|
||||
`Task audit: ${summary.total} findings · ${summary.errors} errors · ${summary.warnings} warnings`,
|
||||
),
|
||||
);
|
||||
if (opts.severity || opts.code) {
|
||||
runtime.log(info(`Showing ${findings.length} matching findings.`));
|
||||
}
|
||||
if (opts.severity) {
|
||||
runtime.log(info(`Severity filter: ${opts.severity}`));
|
||||
}
|
||||
if (opts.code) {
|
||||
runtime.log(info(`Code filter: ${opts.code}`));
|
||||
}
|
||||
if (typeof opts.limit === "number" && opts.limit > 0) {
|
||||
runtime.log(info(`Limit: ${opts.limit}`));
|
||||
}
|
||||
if (displayed.length === 0) {
|
||||
runtime.log("No task audit findings.");
|
||||
return;
|
||||
}
|
||||
for (const line of formatAuditRows(displayed)) {
|
||||
runtime.log(line);
|
||||
}
|
||||
}
|
||||
|
||||
export async function runTasksMaintenance(
|
||||
opts: { json?: boolean; apply?: boolean },
|
||||
deps: TasksCliDeps,
|
||||
runtime: RuntimeEnv,
|
||||
) {
|
||||
const auditBeforeFindings = await deps.operations.audit({
|
||||
namespace: "tasks",
|
||||
});
|
||||
const maintenance = await deps.operations.maintenance({
|
||||
namespace: "tasks",
|
||||
apply: Boolean(opts.apply),
|
||||
});
|
||||
const summary = await deps.operations.summarize({
|
||||
namespace: "tasks",
|
||||
});
|
||||
const auditAfterFindings = opts.apply
|
||||
? await deps.operations.audit({
|
||||
namespace: "tasks",
|
||||
})
|
||||
: auditBeforeFindings;
|
||||
const auditBefore = summarizeAuditFindings(auditBeforeFindings);
|
||||
const auditAfter = summarizeAuditFindings(auditAfterFindings);
|
||||
|
||||
if (opts.json) {
|
||||
runtime.log(
|
||||
JSON.stringify(
|
||||
{
|
||||
mode: opts.apply ? "apply" : "preview",
|
||||
maintenance,
|
||||
tasks: summary,
|
||||
auditBefore,
|
||||
auditAfter,
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
runtime.log(
|
||||
info(
|
||||
`Task maintenance (${opts.apply ? "applied" : "preview"}): ${maintenance.reconciled} reconcile · ${maintenance.cleanupStamped} cleanup stamp · ${maintenance.pruned} prune`,
|
||||
),
|
||||
);
|
||||
runtime.log(
|
||||
info(
|
||||
`${opts.apply ? "Task health after apply" : "Task health"}: ${summary.byStatus.queued ?? 0} queued · ${summary.byStatus.running ?? 0} running · ${auditAfter.errors} audit errors · ${auditAfter.warnings} audit warnings`,
|
||||
),
|
||||
);
|
||||
if (opts.apply) {
|
||||
runtime.log(
|
||||
info(
|
||||
`Task health before apply: ${auditBefore.errors} audit errors · ${auditBefore.warnings} audit warnings`,
|
||||
),
|
||||
);
|
||||
}
|
||||
if (!opts.apply) {
|
||||
runtime.log("Dry run only. Re-run with `openclaw tasks maintenance --apply` to write changes.");
|
||||
}
|
||||
}
|
||||
|
|
@ -1,162 +0,0 @@
|
|||
import type { Command } from "commander";
|
||||
import type { OpenClawConfig, PluginOperationsRuntime } from "openclaw/plugin-sdk/plugin-entry";
|
||||
import { defaultRuntime } from "openclaw/plugin-sdk/runtime";
|
||||
import {
|
||||
runTasksAudit,
|
||||
runTasksCancel,
|
||||
runTasksList,
|
||||
runTasksMaintenance,
|
||||
runTasksNotify,
|
||||
runTasksShow,
|
||||
} from "./cli.runtime.js";
|
||||
|
||||
function parsePositiveIntOrUndefined(value: unknown): number | undefined {
|
||||
if (typeof value !== "string" || !value.trim()) {
|
||||
return undefined;
|
||||
}
|
||||
const parsed = Number.parseInt(value, 10);
|
||||
return Number.isFinite(parsed) && parsed > 0 ? parsed : undefined;
|
||||
}
|
||||
|
||||
export function registerTasksCli(
|
||||
program: Command,
|
||||
deps: {
|
||||
config: OpenClawConfig;
|
||||
operations: PluginOperationsRuntime;
|
||||
},
|
||||
) {
|
||||
const tasks = program
|
||||
.command("tasks")
|
||||
.description("Inspect durable background task state")
|
||||
.option("--json", "Output as JSON", false)
|
||||
.option("--runtime <name>", "Filter by kind (subagent, acp, cron, cli)")
|
||||
.option(
|
||||
"--status <name>",
|
||||
"Filter by status (queued, running, succeeded, failed, timed_out, cancelled, lost)",
|
||||
)
|
||||
.action(async (opts) => {
|
||||
await runTasksList(
|
||||
{
|
||||
json: Boolean(opts.json),
|
||||
runtime: opts.runtime as string | undefined,
|
||||
status: opts.status as string | undefined,
|
||||
},
|
||||
deps,
|
||||
defaultRuntime,
|
||||
);
|
||||
});
|
||||
tasks.enablePositionalOptions();
|
||||
|
||||
tasks
|
||||
.command("list")
|
||||
.description("List tracked background tasks")
|
||||
.option("--json", "Output as JSON", false)
|
||||
.option("--runtime <name>", "Filter by kind (subagent, acp, cron, cli)")
|
||||
.option(
|
||||
"--status <name>",
|
||||
"Filter by status (queued, running, succeeded, failed, timed_out, cancelled, lost)",
|
||||
)
|
||||
.action(async (opts, command) => {
|
||||
const parentOpts = command.parent?.opts() as
|
||||
| {
|
||||
json?: boolean;
|
||||
runtime?: string;
|
||||
status?: string;
|
||||
}
|
||||
| undefined;
|
||||
await runTasksList(
|
||||
{
|
||||
json: Boolean(opts.json || parentOpts?.json),
|
||||
runtime: (opts.runtime as string | undefined) ?? parentOpts?.runtime,
|
||||
status: (opts.status as string | undefined) ?? parentOpts?.status,
|
||||
},
|
||||
deps,
|
||||
defaultRuntime,
|
||||
);
|
||||
});
|
||||
|
||||
tasks
|
||||
.command("audit")
|
||||
.description("Show stale or broken background task runs")
|
||||
.option("--json", "Output as JSON", false)
|
||||
.option("--severity <level>", "Filter by severity (warn, error)")
|
||||
.option("--code <name>", "Filter by finding code")
|
||||
.option("--limit <n>", "Limit displayed findings")
|
||||
.action(async (opts, command) => {
|
||||
const parentOpts = command.parent?.opts() as { json?: boolean } | undefined;
|
||||
await runTasksAudit(
|
||||
{
|
||||
json: Boolean(opts.json || parentOpts?.json),
|
||||
severity: opts.severity as "warn" | "error" | undefined,
|
||||
code: opts.code as string | undefined,
|
||||
limit: parsePositiveIntOrUndefined(opts.limit),
|
||||
},
|
||||
deps,
|
||||
defaultRuntime,
|
||||
);
|
||||
});
|
||||
|
||||
tasks
|
||||
.command("maintenance")
|
||||
.description("Preview or apply task ledger maintenance")
|
||||
.option("--json", "Output as JSON", false)
|
||||
.option("--apply", "Apply reconciliation, cleanup stamping, and pruning", false)
|
||||
.action(async (opts, command) => {
|
||||
const parentOpts = command.parent?.opts() as { json?: boolean } | undefined;
|
||||
await runTasksMaintenance(
|
||||
{
|
||||
json: Boolean(opts.json || parentOpts?.json),
|
||||
apply: Boolean(opts.apply),
|
||||
},
|
||||
deps,
|
||||
defaultRuntime,
|
||||
);
|
||||
});
|
||||
|
||||
tasks
|
||||
.command("show")
|
||||
.description("Show one background task by task id, run id, or session key")
|
||||
.argument("<lookup>", "Task id, run id, or session key")
|
||||
.option("--json", "Output as JSON", false)
|
||||
.action(async (lookup, opts, command) => {
|
||||
const parentOpts = command.parent?.opts() as { json?: boolean } | undefined;
|
||||
await runTasksShow(
|
||||
{
|
||||
lookup,
|
||||
json: Boolean(opts.json || parentOpts?.json),
|
||||
},
|
||||
deps,
|
||||
defaultRuntime,
|
||||
);
|
||||
});
|
||||
|
||||
tasks
|
||||
.command("notify")
|
||||
.description("Set task notify policy")
|
||||
.argument("<lookup>", "Task id, run id, or session key")
|
||||
.argument("<notify>", "Notify policy (done_only, state_changes, silent)")
|
||||
.action(async (lookup, notify) => {
|
||||
await runTasksNotify(
|
||||
{
|
||||
lookup,
|
||||
notify: notify as "done_only" | "state_changes" | "silent",
|
||||
},
|
||||
deps,
|
||||
defaultRuntime,
|
||||
);
|
||||
});
|
||||
|
||||
tasks
|
||||
.command("cancel")
|
||||
.description("Cancel a running background task")
|
||||
.argument("<lookup>", "Task id, run id, or session key")
|
||||
.action(async (lookup) => {
|
||||
await runTasksCancel(
|
||||
{
|
||||
lookup,
|
||||
},
|
||||
deps,
|
||||
defaultRuntime,
|
||||
);
|
||||
});
|
||||
}
|
||||
14
package.json
14
package.json
|
|
@ -185,17 +185,9 @@
|
|||
"types": "./dist/plugin-sdk/plugin-runtime.d.ts",
|
||||
"default": "./dist/plugin-sdk/plugin-runtime.js"
|
||||
},
|
||||
"./plugin-sdk/tasks": {
|
||||
"types": "./dist/plugin-sdk/tasks.d.ts",
|
||||
"default": "./dist/plugin-sdk/tasks.js"
|
||||
},
|
||||
"./plugin-sdk/tasks-summary": {
|
||||
"types": "./dist/plugin-sdk/tasks-summary.d.ts",
|
||||
"default": "./dist/plugin-sdk/tasks-summary.js"
|
||||
},
|
||||
"./plugin-sdk/tasks-empty-summary": {
|
||||
"types": "./dist/plugin-sdk/tasks-empty-summary.d.ts",
|
||||
"default": "./dist/plugin-sdk/tasks-empty-summary.js"
|
||||
"./plugin-sdk/operations-default": {
|
||||
"types": "./dist/plugin-sdk/operations-default.d.ts",
|
||||
"default": "./dist/plugin-sdk/operations-default.js"
|
||||
},
|
||||
"./plugin-sdk/security-runtime": {
|
||||
"types": "./dist/plugin-sdk/security-runtime.d.ts",
|
||||
|
|
|
|||
|
|
@ -1,9 +0,0 @@
|
|||
{
|
||||
"name": "@openclaw/tasks-host-sdk",
|
||||
"version": "0.0.0-private",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"exports": {
|
||||
"./runtime-core": "./src/runtime-core.ts"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
export * from "./flow-registry.js";
|
||||
export * from "./flow-registry.store.js";
|
||||
export * from "./flow-registry.types.js";
|
||||
export * from "./flow-runtime.js";
|
||||
export * from "./operations-runtime.js";
|
||||
export * from "./task-executor.js";
|
||||
export * from "./task-registry.audit.shared.js";
|
||||
export * from "./task-registry.audit.js";
|
||||
export * from "./task-registry.maintenance.js";
|
||||
export * from "./task-registry.store.js";
|
||||
export * from "./task-registry.summary.js";
|
||||
export * from "./task-registry.js";
|
||||
export * from "./task-registry.types.js";
|
||||
|
|
@ -1 +0,0 @@
|
|||
export { sendMessage } from "../../../src/infra/outbound/message.js";
|
||||
|
|
@ -36,9 +36,7 @@
|
|||
"speech-runtime",
|
||||
"speech-core",
|
||||
"plugin-runtime",
|
||||
"tasks",
|
||||
"tasks-empty-summary",
|
||||
"tasks-summary",
|
||||
"operations-default",
|
||||
"security-runtime",
|
||||
"gateway-runtime",
|
||||
"github-copilot-login",
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
import {
|
||||
createRunningTaskRun,
|
||||
completeTaskRunByRunId,
|
||||
failTaskRunByRunId,
|
||||
startTaskRunByRunId,
|
||||
} from "openclaw/plugin-sdk/tasks";
|
||||
import { resolveAgentTimeoutMs } from "../../agents/timeout.js";
|
||||
import type { OpenClawConfig } from "../../config/config.js";
|
||||
import { logVerbose } from "../../globals.js";
|
||||
import { normalizeAgentId } from "../../routing/session-key.js";
|
||||
import { isAcpSessionKey } from "../../sessions/session-key-utils.js";
|
||||
import {
|
||||
createRunningTaskRun,
|
||||
completeTaskRunByRunId,
|
||||
failTaskRunByRunId,
|
||||
startTaskRunByRunId,
|
||||
} from "../../tasks/task-executor.js";
|
||||
import type { DeliveryContext } from "../../utils/delivery-context.js";
|
||||
import {
|
||||
AcpRuntimeError,
|
||||
|
|
|
|||
|
|
@ -37,9 +37,9 @@ vi.mock("../runtime/registry.js", async (importOriginal) => {
|
|||
let AcpSessionManager: typeof import("./manager.js").AcpSessionManager;
|
||||
let AcpRuntimeError: typeof import("../runtime/errors.js").AcpRuntimeError;
|
||||
let resetAcpSessionManagerForTests: typeof import("./manager.js").__testing.resetAcpSessionManagerForTests;
|
||||
let findTaskByRunId: typeof import("openclaw/plugin-sdk/tasks").findTaskByRunId;
|
||||
let resetTaskRegistryForTests: typeof import("openclaw/plugin-sdk/tasks").resetTaskRegistryForTests;
|
||||
let resetFlowRegistryForTests: typeof import("openclaw/plugin-sdk/tasks").resetFlowRegistryForTests;
|
||||
let findTaskByRunId: typeof import("../../tasks/task-registry.js").findTaskByRunId;
|
||||
let resetTaskRegistryForTests: typeof import("../../tasks/task-registry.js").resetTaskRegistryForTests;
|
||||
let resetFlowRegistryForTests: typeof import("../../tasks/flow-registry.js").resetFlowRegistryForTests;
|
||||
let installInMemoryTaskAndFlowRegistryRuntime: typeof import("../../test-utils/task-flow-registry-runtime.js").installInMemoryTaskAndFlowRegistryRuntime;
|
||||
|
||||
const baseCfg = {
|
||||
|
|
@ -184,8 +184,8 @@ describe("AcpSessionManager", () => {
|
|||
__testing: { resetAcpSessionManagerForTests },
|
||||
} = await import("./manager.js"));
|
||||
({ AcpRuntimeError } = await import("../runtime/errors.js"));
|
||||
({ findTaskByRunId, resetTaskRegistryForTests } = await import("openclaw/plugin-sdk/tasks"));
|
||||
({ resetFlowRegistryForTests } = await import("openclaw/plugin-sdk/tasks"));
|
||||
({ findTaskByRunId, resetTaskRegistryForTests } = await import("../../tasks/task-registry.js"));
|
||||
({ resetFlowRegistryForTests } = await import("../../tasks/flow-registry.js"));
|
||||
({ installInMemoryTaskAndFlowRegistryRuntime } =
|
||||
await import("../../test-utils/task-flow-registry-runtime.js"));
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
import { appendFile, mkdir } from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { recordTaskRunProgressByRunId } from "openclaw/plugin-sdk/tasks";
|
||||
import { readAcpSessionEntry } from "../acp/runtime/session-meta.js";
|
||||
import { resolveSessionFilePath, resolveSessionFilePathOptions } from "../config/sessions/paths.js";
|
||||
import { onAgentEvent } from "../infra/agent-events.js";
|
||||
import { requestHeartbeatNow } from "../infra/heartbeat-wake.js";
|
||||
import { enqueueSystemEvent } from "../infra/system-events.js";
|
||||
import { scopedHeartbeatWakeOptions } from "../routing/session-key.js";
|
||||
import { recordTaskRunProgressByRunId } from "../tasks/task-executor.js";
|
||||
|
||||
const DEFAULT_STREAM_FLUSH_MS = 2_500;
|
||||
const DEFAULT_NO_OUTPUT_NOTICE_MS = 60_000;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import { resetTaskRegistryForTests } from "openclaw/plugin-sdk/tasks";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import * as acpSessionManager from "../acp/control-plane/manager.js";
|
||||
import type { AcpInitializeSessionInput } from "../acp/control-plane/manager.types.js";
|
||||
|
|
@ -19,6 +18,7 @@ import {
|
|||
type SessionBindingPlacement,
|
||||
type SessionBindingRecord,
|
||||
} from "../infra/outbound/session-binding-service.js";
|
||||
import { resetTaskRegistryForTests } from "../tasks/task-registry.js";
|
||||
import * as acpSpawnParentStream from "./acp-spawn-parent-stream.js";
|
||||
|
||||
function createDefaultSpawnConfig(): OpenClawConfig {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import crypto from "node:crypto";
|
||||
import { createRunningTaskRun } from "openclaw/plugin-sdk/tasks";
|
||||
import { getAcpSessionManager } from "../acp/control-plane/manager.js";
|
||||
import {
|
||||
cleanupFailedAcpSpawn,
|
||||
|
|
@ -45,6 +44,7 @@ import {
|
|||
normalizeAgentId,
|
||||
parseAgentSessionKey,
|
||||
} from "../routing/session-key.js";
|
||||
import { createRunningTaskRun } from "../tasks/task-executor.js";
|
||||
import {
|
||||
deliveryContextFromSession,
|
||||
formatConversationTarget,
|
||||
|
|
|
|||
|
|
@ -192,7 +192,7 @@ async function loadFreshOpenClawToolsForSessionStatusTest() {
|
|||
vi.doMock("../auto-reply/status.js", () => ({
|
||||
buildStatusMessage: buildStatusMessageMock,
|
||||
}));
|
||||
vi.doMock("openclaw/plugin-sdk/tasks", () => ({
|
||||
vi.doMock("../tasks/task-registry.js", () => ({
|
||||
listTasksForSessionKey: (sessionKey: string) => listTasksForSessionKeyMock(sessionKey),
|
||||
}));
|
||||
({ createSessionStatusTool } = await import("./tools/session-status-tool.js"));
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import { isSilentReplyText, SILENT_REPLY_TOKEN } from "../auto-reply/tokens.js";
|
||||
import { defaultRuntime } from "../runtime.js";
|
||||
import { emitSessionLifecycleEvent } from "../sessions/session-lifecycle-events.js";
|
||||
import {
|
||||
completeTaskRunByRunId,
|
||||
failTaskRunByRunId,
|
||||
setDetachedTaskDeliveryStatusByRunId,
|
||||
} from "openclaw/plugin-sdk/tasks";
|
||||
import { isSilentReplyText, SILENT_REPLY_TOKEN } from "../auto-reply/tokens.js";
|
||||
import { defaultRuntime } from "../runtime.js";
|
||||
import { emitSessionLifecycleEvent } from "../sessions/session-lifecycle-events.js";
|
||||
} from "../tasks/task-executor.js";
|
||||
import { normalizeDeliveryContext } from "../utils/delivery-context.js";
|
||||
import {
|
||||
captureSubagentCompletionReply,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { createRunningTaskRun } from "openclaw/plugin-sdk/tasks";
|
||||
import { loadConfig } from "../config/config.js";
|
||||
import { callGateway } from "../gateway/call.js";
|
||||
import { createSubsystemLogger } from "../logging/subsystem.js";
|
||||
import { createRunningTaskRun } from "../tasks/task-executor.js";
|
||||
import { type DeliveryContext, normalizeDeliveryContext } from "../utils/delivery-context.js";
|
||||
import { ensureRuntimePluginsLoaded } from "./runtime-plugins.js";
|
||||
import type { SubagentRunOutcome } from "./subagent-announce.js";
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import { Type } from "@sinclair/typebox";
|
||||
import { listTasksForSessionKey } from "openclaw/plugin-sdk/tasks";
|
||||
import { normalizeGroupActivation } from "../../auto-reply/group-activation.js";
|
||||
import { getFollowupQueueDepth, resolveQueueSettings } from "../../auto-reply/reply/queue.js";
|
||||
import { buildStatusMessage } from "../../auto-reply/status.js";
|
||||
|
|
@ -24,6 +23,7 @@ import {
|
|||
resolveAgentIdFromSessionKey,
|
||||
} from "../../routing/session-key.js";
|
||||
import { applyModelOverrideToSessionEntry } from "../../sessions/model-overrides.js";
|
||||
import { listTasksForSessionKey } from "../../tasks/task-registry.js";
|
||||
import { resolveAgentConfig, resolveAgentDir } from "../agent-scope.js";
|
||||
import { formatUserTime, resolveUserTimeFormat, resolveUserTimezone } from "../date-time.js";
|
||||
import { resolveModelAuthLabel } from "../model-auth-label.js";
|
||||
|
|
|
|||
|
|
@ -114,7 +114,8 @@ const { handleAcpCommand } = await import("./commands-acp.js");
|
|||
const { buildCommandTestParams } = await import("./commands-spawn.test-harness.js");
|
||||
const { __testing: acpManagerTesting } = await import("../../acp/control-plane/manager.js");
|
||||
const { __testing: acpResetTargetTesting } = await import("./acp-reset-target.js");
|
||||
const { createTaskRecord, resetTaskRegistryForTests } = await import("openclaw/plugin-sdk/tasks");
|
||||
const { createTaskRecord, resetTaskRegistryForTests } =
|
||||
await import("../../tasks/task-registry.js");
|
||||
|
||||
function parseTelegramChatIdForTest(raw?: string | null): string | undefined {
|
||||
const trimmed = raw?.trim().replace(/^telegram:/i, "");
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import { findLatestTaskForSessionKey } from "openclaw/plugin-sdk/tasks";
|
||||
import { getAcpSessionManager } from "../../../acp/control-plane/manager.js";
|
||||
import {
|
||||
parseRuntimeTimeoutSecondsInput,
|
||||
|
|
@ -9,6 +8,7 @@ import {
|
|||
validateRuntimePermissionProfileInput,
|
||||
} from "../../../acp/control-plane/runtime-options.js";
|
||||
import { resolveAcpSessionIdentifierLinesFromIdentity } from "../../../acp/runtime/session-identifiers.js";
|
||||
import { findLatestTaskForSessionKey } from "../../../tasks/task-registry.js";
|
||||
import type { CommandHandlerResult, HandleCommandsParams } from "../commands-types.js";
|
||||
import {
|
||||
ACP_CWD_USAGE,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { findTaskByRunId } from "openclaw/plugin-sdk/tasks";
|
||||
import { countPendingDescendantRuns } from "../../../agents/subagent-registry.js";
|
||||
import { loadSessionStore, resolveStorePath } from "../../../config/sessions.js";
|
||||
import { formatDurationCompact } from "../../../shared/subagents-format.js";
|
||||
import { findTaskByRunId } from "../../../tasks/task-registry.js";
|
||||
import type { CommandHandlerResult } from "../commands-types.js";
|
||||
import { formatRunLabel } from "../subagents-utils.js";
|
||||
import {
|
||||
|
|
|
|||
|
|
@ -249,7 +249,8 @@ const { parseConfigCommand } = await import("./config-commands.js");
|
|||
const { parseDebugCommand } = await import("./debug-commands.js");
|
||||
const { parseInlineDirectives } = await import("./directive-handling.js");
|
||||
const { buildCommandContext, handleCommands } = await import("./commands.js");
|
||||
const { createTaskRecord, resetTaskRegistryForTests } = await import("openclaw/plugin-sdk/tasks");
|
||||
const { createTaskRecord, resetTaskRegistryForTests } =
|
||||
await import("../../tasks/task-registry.js");
|
||||
|
||||
let testWorkspaceDir = os.tmpdir();
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,12 @@ const mocks = vi.hoisted(() => ({
|
|||
flowsCancelCommand: vi.fn(),
|
||||
sessionsCommand: vi.fn(),
|
||||
sessionsCleanupCommand: vi.fn(),
|
||||
tasksListCommand: vi.fn(),
|
||||
tasksAuditCommand: vi.fn(),
|
||||
tasksMaintenanceCommand: vi.fn(),
|
||||
tasksShowCommand: vi.fn(),
|
||||
tasksNotifyCommand: vi.fn(),
|
||||
tasksCancelCommand: vi.fn(),
|
||||
setVerbose: vi.fn(),
|
||||
runtime: {
|
||||
log: vi.fn(),
|
||||
|
|
@ -25,6 +31,12 @@ const flowsShowCommand = mocks.flowsShowCommand;
|
|||
const flowsCancelCommand = mocks.flowsCancelCommand;
|
||||
const sessionsCommand = mocks.sessionsCommand;
|
||||
const sessionsCleanupCommand = mocks.sessionsCleanupCommand;
|
||||
const tasksListCommand = mocks.tasksListCommand;
|
||||
const tasksAuditCommand = mocks.tasksAuditCommand;
|
||||
const tasksMaintenanceCommand = mocks.tasksMaintenanceCommand;
|
||||
const tasksShowCommand = mocks.tasksShowCommand;
|
||||
const tasksNotifyCommand = mocks.tasksNotifyCommand;
|
||||
const tasksCancelCommand = mocks.tasksCancelCommand;
|
||||
const setVerbose = mocks.setVerbose;
|
||||
const runtime = mocks.runtime;
|
||||
|
||||
|
|
@ -50,6 +62,15 @@ vi.mock("../../commands/sessions-cleanup.js", () => ({
|
|||
sessionsCleanupCommand: mocks.sessionsCleanupCommand,
|
||||
}));
|
||||
|
||||
vi.mock("../../commands/tasks.js", () => ({
|
||||
tasksListCommand: mocks.tasksListCommand,
|
||||
tasksAuditCommand: mocks.tasksAuditCommand,
|
||||
tasksMaintenanceCommand: mocks.tasksMaintenanceCommand,
|
||||
tasksShowCommand: mocks.tasksShowCommand,
|
||||
tasksNotifyCommand: mocks.tasksNotifyCommand,
|
||||
tasksCancelCommand: mocks.tasksCancelCommand,
|
||||
}));
|
||||
|
||||
vi.mock("../../globals.js", () => ({
|
||||
setVerbose: mocks.setVerbose,
|
||||
}));
|
||||
|
|
@ -75,6 +96,12 @@ describe("registerStatusHealthSessionsCommands", () => {
|
|||
flowsCancelCommand.mockResolvedValue(undefined);
|
||||
sessionsCommand.mockResolvedValue(undefined);
|
||||
sessionsCleanupCommand.mockResolvedValue(undefined);
|
||||
tasksListCommand.mockResolvedValue(undefined);
|
||||
tasksAuditCommand.mockResolvedValue(undefined);
|
||||
tasksMaintenanceCommand.mockResolvedValue(undefined);
|
||||
tasksShowCommand.mockResolvedValue(undefined);
|
||||
tasksNotifyCommand.mockResolvedValue(undefined);
|
||||
tasksCancelCommand.mockResolvedValue(undefined);
|
||||
});
|
||||
|
||||
it("runs status command with timeout and debug-derived verbose", async () => {
|
||||
|
|
@ -222,6 +249,90 @@ describe("registerStatusHealthSessionsCommands", () => {
|
|||
);
|
||||
});
|
||||
|
||||
it("runs tasks list from the parent command", async () => {
|
||||
await runCli(["tasks", "--json", "--runtime", "acp", "--status", "running"]);
|
||||
|
||||
expect(tasksListCommand).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
json: true,
|
||||
runtime: "acp",
|
||||
status: "running",
|
||||
}),
|
||||
runtime,
|
||||
);
|
||||
});
|
||||
|
||||
it("runs tasks show subcommand with lookup forwarding", async () => {
|
||||
await runCli(["tasks", "show", "run-123", "--json"]);
|
||||
|
||||
expect(tasksShowCommand).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
lookup: "run-123",
|
||||
json: true,
|
||||
}),
|
||||
runtime,
|
||||
);
|
||||
});
|
||||
|
||||
it("runs tasks maintenance subcommand with apply forwarding", async () => {
|
||||
await runCli(["tasks", "--json", "maintenance", "--apply"]);
|
||||
|
||||
expect(tasksMaintenanceCommand).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
json: true,
|
||||
apply: true,
|
||||
}),
|
||||
runtime,
|
||||
);
|
||||
});
|
||||
|
||||
it("runs tasks audit subcommand with filters", async () => {
|
||||
await runCli([
|
||||
"tasks",
|
||||
"--json",
|
||||
"audit",
|
||||
"--severity",
|
||||
"error",
|
||||
"--code",
|
||||
"stale_running",
|
||||
"--limit",
|
||||
"5",
|
||||
]);
|
||||
|
||||
expect(tasksAuditCommand).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
json: true,
|
||||
severity: "error",
|
||||
code: "stale_running",
|
||||
limit: 5,
|
||||
}),
|
||||
runtime,
|
||||
);
|
||||
});
|
||||
|
||||
it("runs tasks notify subcommand with lookup and policy forwarding", async () => {
|
||||
await runCli(["tasks", "notify", "run-123", "state_changes"]);
|
||||
|
||||
expect(tasksNotifyCommand).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
lookup: "run-123",
|
||||
notify: "state_changes",
|
||||
}),
|
||||
runtime,
|
||||
);
|
||||
});
|
||||
|
||||
it("runs tasks cancel subcommand with lookup forwarding", async () => {
|
||||
await runCli(["tasks", "cancel", "run-123"]);
|
||||
|
||||
expect(tasksCancelCommand).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
lookup: "run-123",
|
||||
}),
|
||||
runtime,
|
||||
);
|
||||
});
|
||||
|
||||
it("runs flows list from the parent command", async () => {
|
||||
await runCli(["flows", "--json", "--status", "blocked"]);
|
||||
|
||||
|
|
|
|||
|
|
@ -4,12 +4,21 @@ import { healthCommand } from "../../commands/health.js";
|
|||
import { sessionsCleanupCommand } from "../../commands/sessions-cleanup.js";
|
||||
import { sessionsCommand } from "../../commands/sessions.js";
|
||||
import { statusCommand } from "../../commands/status.js";
|
||||
import {
|
||||
tasksAuditCommand,
|
||||
tasksCancelCommand,
|
||||
tasksListCommand,
|
||||
tasksMaintenanceCommand,
|
||||
tasksNotifyCommand,
|
||||
tasksShowCommand,
|
||||
} from "../../commands/tasks.js";
|
||||
import { setVerbose } from "../../globals.js";
|
||||
import { defaultRuntime } from "../../runtime.js";
|
||||
import { formatDocsLink } from "../../terminal/links.js";
|
||||
import { theme } from "../../terminal/theme.js";
|
||||
import { runCommandWithRuntime } from "../cli-utils.js";
|
||||
import { formatHelpExamples } from "../help-format.js";
|
||||
import { parsePositiveIntOrUndefined } from "./helpers.js";
|
||||
|
||||
function resolveVerbose(opts: { verbose?: boolean; debug?: boolean }): boolean {
|
||||
return Boolean(opts.verbose || opts.debug);
|
||||
|
|
@ -218,6 +227,159 @@ export function registerStatusHealthSessionsCommands(program: Command) {
|
|||
);
|
||||
});
|
||||
});
|
||||
|
||||
const tasksCmd = program
|
||||
.command("tasks")
|
||||
.description("Inspect durable background task state")
|
||||
.option("--json", "Output as JSON", false)
|
||||
.option("--runtime <name>", "Filter by kind (subagent, acp, cron, cli)")
|
||||
.option(
|
||||
"--status <name>",
|
||||
"Filter by status (queued, running, succeeded, failed, timed_out, cancelled, lost)",
|
||||
)
|
||||
.action(async (opts) => {
|
||||
await runCommandWithRuntime(defaultRuntime, async () => {
|
||||
await tasksListCommand(
|
||||
{
|
||||
json: Boolean(opts.json),
|
||||
runtime: opts.runtime as string | undefined,
|
||||
status: opts.status as string | undefined,
|
||||
},
|
||||
defaultRuntime,
|
||||
);
|
||||
});
|
||||
});
|
||||
tasksCmd.enablePositionalOptions();
|
||||
|
||||
tasksCmd
|
||||
.command("list")
|
||||
.description("List tracked background tasks")
|
||||
.option("--json", "Output as JSON", false)
|
||||
.option("--runtime <name>", "Filter by kind (subagent, acp, cron, cli)")
|
||||
.option(
|
||||
"--status <name>",
|
||||
"Filter by status (queued, running, succeeded, failed, timed_out, cancelled, lost)",
|
||||
)
|
||||
.action(async (opts, command) => {
|
||||
const parentOpts = command.parent?.opts() as
|
||||
| {
|
||||
json?: boolean;
|
||||
runtime?: string;
|
||||
status?: string;
|
||||
}
|
||||
| undefined;
|
||||
await runCommandWithRuntime(defaultRuntime, async () => {
|
||||
await tasksListCommand(
|
||||
{
|
||||
json: Boolean(opts.json || parentOpts?.json),
|
||||
runtime: (opts.runtime as string | undefined) ?? parentOpts?.runtime,
|
||||
status: (opts.status as string | undefined) ?? parentOpts?.status,
|
||||
},
|
||||
defaultRuntime,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
tasksCmd
|
||||
.command("audit")
|
||||
.description("Show stale or broken background task runs")
|
||||
.option("--json", "Output as JSON", false)
|
||||
.option("--severity <level>", "Filter by severity (warn, error)")
|
||||
.option(
|
||||
"--code <name>",
|
||||
"Filter by finding code (stale_queued, stale_running, lost, delivery_failed, missing_cleanup, inconsistent_timestamps)",
|
||||
)
|
||||
.option("--limit <n>", "Limit displayed findings")
|
||||
.action(async (opts, command) => {
|
||||
const parentOpts = command.parent?.opts() as { json?: boolean } | undefined;
|
||||
await runCommandWithRuntime(defaultRuntime, async () => {
|
||||
await tasksAuditCommand(
|
||||
{
|
||||
json: Boolean(opts.json || parentOpts?.json),
|
||||
severity: opts.severity as "warn" | "error" | undefined,
|
||||
code: opts.code as
|
||||
| "stale_queued"
|
||||
| "stale_running"
|
||||
| "lost"
|
||||
| "delivery_failed"
|
||||
| "missing_cleanup"
|
||||
| "inconsistent_timestamps"
|
||||
| undefined,
|
||||
limit: parsePositiveIntOrUndefined(opts.limit),
|
||||
},
|
||||
defaultRuntime,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
tasksCmd
|
||||
.command("maintenance")
|
||||
.description("Preview or apply task ledger maintenance")
|
||||
.option("--json", "Output as JSON", false)
|
||||
.option("--apply", "Apply reconciliation, cleanup stamping, and pruning", false)
|
||||
.action(async (opts, command) => {
|
||||
const parentOpts = command.parent?.opts() as { json?: boolean } | undefined;
|
||||
await runCommandWithRuntime(defaultRuntime, async () => {
|
||||
await tasksMaintenanceCommand(
|
||||
{
|
||||
json: Boolean(opts.json || parentOpts?.json),
|
||||
apply: Boolean(opts.apply),
|
||||
},
|
||||
defaultRuntime,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
tasksCmd
|
||||
.command("show")
|
||||
.description("Show one background task by task id, run id, or session key")
|
||||
.argument("<lookup>", "Task id, run id, or session key")
|
||||
.option("--json", "Output as JSON", false)
|
||||
.action(async (lookup, opts, command) => {
|
||||
const parentOpts = command.parent?.opts() as { json?: boolean } | undefined;
|
||||
await runCommandWithRuntime(defaultRuntime, async () => {
|
||||
await tasksShowCommand(
|
||||
{
|
||||
lookup,
|
||||
json: Boolean(opts.json || parentOpts?.json),
|
||||
},
|
||||
defaultRuntime,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
tasksCmd
|
||||
.command("notify")
|
||||
.description("Set task notify policy")
|
||||
.argument("<lookup>", "Task id, run id, or session key")
|
||||
.argument("<notify>", "Notify policy (done_only, state_changes, silent)")
|
||||
.action(async (lookup, notify) => {
|
||||
await runCommandWithRuntime(defaultRuntime, async () => {
|
||||
await tasksNotifyCommand(
|
||||
{
|
||||
lookup,
|
||||
notify: notify as "done_only" | "state_changes" | "silent",
|
||||
},
|
||||
defaultRuntime,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
tasksCmd
|
||||
.command("cancel")
|
||||
.description("Cancel a running background task")
|
||||
.argument("<lookup>", "Task id, run id, or session key")
|
||||
.action(async (lookup) => {
|
||||
await runCommandWithRuntime(defaultRuntime, async () => {
|
||||
await tasksCancelCommand(
|
||||
{
|
||||
lookup,
|
||||
},
|
||||
defaultRuntime,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
const flowsCmd = program
|
||||
.command("flows")
|
||||
.description("Inspect ClawFlow state")
|
||||
|
|
|
|||
|
|
@ -52,11 +52,11 @@ vi.mock("../plugins/memory-state.js", () => ({
|
|||
hasMemoryRuntime: hasMemoryRuntimeMock,
|
||||
}));
|
||||
|
||||
vi.mock("openclaw/plugin-sdk/tasks", () => ({
|
||||
vi.mock("../tasks/task-registry.js", () => ({
|
||||
ensureTaskRegistryReady: ensureTaskRegistryReadyMock,
|
||||
}));
|
||||
|
||||
vi.mock("openclaw/plugin-sdk/tasks", () => ({
|
||||
vi.mock("../tasks/task-registry.maintenance.js", () => ({
|
||||
startTaskRegistryMaintenance: startTaskRegistryMaintenanceMock,
|
||||
}));
|
||||
|
||||
|
|
|
|||
|
|
@ -32,11 +32,11 @@ vi.mock("../plugins/status.js", () => ({
|
|||
mocks.buildPluginCompatibilityWarnings(...args),
|
||||
}));
|
||||
|
||||
vi.mock("openclaw/plugin-sdk/tasks", () => ({
|
||||
vi.mock("../tasks/flow-registry.js", () => ({
|
||||
listFlowRecords: (...args: unknown[]) => mocks.listFlowRecords(...args),
|
||||
}));
|
||||
|
||||
vi.mock("openclaw/plugin-sdk/tasks", () => ({
|
||||
vi.mock("../tasks/task-registry.js", () => ({
|
||||
listTasksForFlowId: (...args: unknown[]) => mocks.listTasksForFlowId(...args),
|
||||
}));
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import { listFlowRecords } from "openclaw/plugin-sdk/tasks";
|
||||
import { listTasksForFlowId } from "openclaw/plugin-sdk/tasks";
|
||||
import { resolveAgentWorkspaceDir, resolveDefaultAgentId } from "../agents/agent-scope.js";
|
||||
import { buildWorkspaceSkillStatus } from "../agents/skills-status.js";
|
||||
import { formatCliCommand } from "../cli/command-format.js";
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import { buildPluginCompatibilityWarnings, buildPluginStatusReport } from "../plugins/status.js";
|
||||
import { listFlowRecords } from "../tasks/flow-registry.js";
|
||||
import { listTasksForFlowId } from "../tasks/task-registry.js";
|
||||
import { note } from "../terminal/note.js";
|
||||
import { detectLegacyWorkspaceDirs, formatLegacyWorkspaceWarning } from "./doctor-workspace.js";
|
||||
|
||||
|
|
|
|||
|
|
@ -12,11 +12,17 @@ const mocks = vi.hoisted(() => ({
|
|||
loadConfigMock: vi.fn(() => ({ loaded: true })),
|
||||
}));
|
||||
|
||||
vi.mock("openclaw/plugin-sdk/tasks", () => ({
|
||||
vi.mock("../tasks/flow-registry.js", () => ({
|
||||
listFlowRecords: (...args: unknown[]) => mocks.listFlowRecordsMock(...args),
|
||||
resolveFlowForLookupToken: (...args: unknown[]) => mocks.resolveFlowForLookupTokenMock(...args),
|
||||
getFlowById: (...args: unknown[]) => mocks.getFlowByIdMock(...args),
|
||||
}));
|
||||
|
||||
vi.mock("../tasks/task-registry.js", () => ({
|
||||
listTasksForFlowId: (...args: unknown[]) => mocks.listTasksForFlowIdMock(...args),
|
||||
}));
|
||||
|
||||
vi.mock("../tasks/task-executor.js", () => ({
|
||||
getFlowTaskSummary: (...args: unknown[]) => mocks.getFlowTaskSummaryMock(...args),
|
||||
cancelFlowById: (...args: unknown[]) => mocks.cancelFlowByIdMock(...args),
|
||||
}));
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import { getFlowById, listFlowRecords, resolveFlowForLookupToken } from "openclaw/plugin-sdk/tasks";
|
||||
import type { FlowRecord, FlowStatus } from "openclaw/plugin-sdk/tasks";
|
||||
import { cancelFlowById, getFlowTaskSummary } from "openclaw/plugin-sdk/tasks";
|
||||
import { listTasksForFlowId } from "openclaw/plugin-sdk/tasks";
|
||||
import { loadConfig } from "../config/config.js";
|
||||
import { info } from "../globals.js";
|
||||
import type { RuntimeEnv } from "../runtime.js";
|
||||
import { getFlowById, listFlowRecords, resolveFlowForLookupToken } from "../tasks/flow-registry.js";
|
||||
import type { FlowRecord, FlowStatus } from "../tasks/flow-registry.types.js";
|
||||
import { cancelFlowById, getFlowTaskSummary } from "../tasks/task-executor.js";
|
||||
import { listTasksForFlowId } from "../tasks/task-registry.js";
|
||||
import { isRich, theme } from "../terminal/theme.js";
|
||||
|
||||
const ID_PAD = 10;
|
||||
|
|
|
|||
|
|
@ -1,12 +1,10 @@
|
|||
import {
|
||||
createEmptyTaskAuditSummary,
|
||||
createEmptyTaskRegistrySummary,
|
||||
} from "openclaw/plugin-sdk/tasks-empty-summary";
|
||||
import type { OpenClawConfig } from "../config/types.js";
|
||||
import type { UpdateCheckResult } from "../infra/update-check.js";
|
||||
import { loggingState } from "../logging/state.js";
|
||||
import { runExec } from "../process/exec.js";
|
||||
import type { RuntimeEnv } from "../runtime.js";
|
||||
import { createEmptyTaskAuditSummary } from "../tasks/task-registry.audit.shared.js";
|
||||
import { createEmptyTaskRegistrySummary } from "../tasks/task-registry.summary.js";
|
||||
import type { getAgentLocalStatuses as getAgentLocalStatusesFn } from "./status.agent-local.js";
|
||||
import type { StatusScanResult } from "./status.scan.js";
|
||||
import {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,4 @@
|
|||
import { existsSync } from "node:fs";
|
||||
import {
|
||||
createEmptyTaskAuditSummary,
|
||||
createEmptyTaskRegistrySummary,
|
||||
} from "openclaw/plugin-sdk/tasks-empty-summary";
|
||||
import { resolveMemorySearchConfig } from "../agents/memory-search.js";
|
||||
import { hasPotentialConfiguredChannels } from "../channels/config-presence.js";
|
||||
import { resolveCommandSecretRefsViaGateway } from "../cli/command-secret-gateway.js";
|
||||
|
|
@ -21,6 +17,8 @@ import {
|
|||
import { runExec } from "../process/exec.js";
|
||||
import type { RuntimeEnv } from "../runtime.js";
|
||||
import { createLazyRuntimeSurface } from "../shared/lazy-runtime.js";
|
||||
import { createEmptyTaskAuditSummary } from "../tasks/task-registry.audit.shared.js";
|
||||
import { createEmptyTaskRegistrySummary } from "../tasks/task-registry.summary.js";
|
||||
import type { buildChannelsTable as buildChannelsTableFn } from "./status-all/channels.js";
|
||||
import type { getAgentLocalStatuses as getAgentLocalStatusesFn } from "./status.agent-local.js";
|
||||
import { buildColdStartUpdateResult, scanStatusJsonCore } from "./status.scan.json-core.js";
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ vi.mock("../infra/system-events.js", () => ({
|
|||
peekSystemEvents: vi.fn(() => []),
|
||||
}));
|
||||
|
||||
vi.mock("openclaw/plugin-sdk/tasks-summary", () => ({
|
||||
vi.mock("../tasks/task-registry.maintenance.js", () => ({
|
||||
getInspectableTaskRegistrySummary: vi.fn(() => ({
|
||||
total: 0,
|
||||
active: 0,
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ let channelSummaryModulePromise: Promise<typeof import("../infra/channel-summary
|
|||
let linkChannelModulePromise: Promise<typeof import("./status.link-channel.js")> | undefined;
|
||||
let configIoModulePromise: Promise<typeof import("../config/io.js")> | undefined;
|
||||
let taskRegistryMaintenanceModulePromise:
|
||||
| Promise<typeof import("openclaw/plugin-sdk/tasks-summary")>
|
||||
| Promise<typeof import("../tasks/task-registry.maintenance.js")>
|
||||
| undefined;
|
||||
|
||||
function loadChannelSummaryModule() {
|
||||
|
|
@ -41,7 +41,7 @@ function loadConfigIoModule() {
|
|||
}
|
||||
|
||||
function loadTaskRegistryMaintenanceModule() {
|
||||
taskRegistryMaintenanceModulePromise ??= import("openclaw/plugin-sdk/tasks-summary");
|
||||
taskRegistryMaintenanceModulePromise ??= import("../tasks/task-registry.maintenance.js");
|
||||
return taskRegistryMaintenanceModulePromise;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -467,7 +467,7 @@ vi.mock("../daemon/node-service.js", () => ({
|
|||
vi.mock("../node-host/config.js", () => ({
|
||||
loadNodeHostConfig: mocks.loadNodeHostConfig,
|
||||
}));
|
||||
vi.mock("openclaw/plugin-sdk/tasks", () => ({
|
||||
vi.mock("../tasks/task-registry.maintenance.js", () => ({
|
||||
getInspectableTaskRegistrySummary: mocks.getInspectableTaskRegistrySummary,
|
||||
getInspectableTaskAuditSummary: mocks.getInspectableTaskAuditSummary,
|
||||
}));
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import type { TaskAuditSummary } from "openclaw/plugin-sdk/tasks-summary";
|
||||
import type { TaskRegistrySummary } from "openclaw/plugin-sdk/tasks-summary";
|
||||
import type { ChannelId } from "../channels/plugins/types.js";
|
||||
import type { TaskAuditSummary } from "../tasks/task-registry.audit.js";
|
||||
import type { TaskRegistrySummary } from "../tasks/task-registry.types.js";
|
||||
|
||||
export type SessionStatus = {
|
||||
agentId?: string;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,424 @@
|
|||
import { loadConfig } from "../config/config.js";
|
||||
import { info } from "../globals.js";
|
||||
import type { RuntimeEnv } from "../runtime.js";
|
||||
import {
|
||||
listTaskAuditFindings,
|
||||
summarizeTaskAuditFindings,
|
||||
type TaskAuditCode,
|
||||
type TaskAuditFinding,
|
||||
type TaskAuditSeverity,
|
||||
} from "../tasks/task-registry.audit.js";
|
||||
import { cancelTaskById, getTaskById, updateTaskNotifyPolicyById } from "../tasks/task-registry.js";
|
||||
import {
|
||||
getInspectableTaskAuditSummary,
|
||||
getInspectableTaskRegistrySummary,
|
||||
previewTaskRegistryMaintenance,
|
||||
runTaskRegistryMaintenance,
|
||||
} from "../tasks/task-registry.maintenance.js";
|
||||
import {
|
||||
reconcileInspectableTasks,
|
||||
reconcileTaskLookupToken,
|
||||
} from "../tasks/task-registry.reconcile.js";
|
||||
import { summarizeTaskRecords } from "../tasks/task-registry.summary.js";
|
||||
import type { TaskNotifyPolicy, TaskRecord } from "../tasks/task-registry.types.js";
|
||||
import { isRich, theme } from "../terminal/theme.js";
|
||||
|
||||
const RUNTIME_PAD = 8;
|
||||
const STATUS_PAD = 10;
|
||||
const DELIVERY_PAD = 14;
|
||||
const ID_PAD = 10;
|
||||
const RUN_PAD = 10;
|
||||
|
||||
function truncate(value: string, maxChars: number) {
|
||||
if (value.length <= maxChars) {
|
||||
return value;
|
||||
}
|
||||
if (maxChars <= 1) {
|
||||
return value.slice(0, maxChars);
|
||||
}
|
||||
return `${value.slice(0, maxChars - 1)}…`;
|
||||
}
|
||||
|
||||
function shortToken(value: string | undefined, maxChars = ID_PAD): string {
|
||||
const trimmed = value?.trim();
|
||||
if (!trimmed) {
|
||||
return "n/a";
|
||||
}
|
||||
return truncate(trimmed, maxChars);
|
||||
}
|
||||
|
||||
function formatTaskStatusCell(status: string, rich: boolean) {
|
||||
const padded = status.padEnd(STATUS_PAD);
|
||||
if (!rich) {
|
||||
return padded;
|
||||
}
|
||||
if (status === "succeeded") {
|
||||
return theme.success(padded);
|
||||
}
|
||||
if (status === "failed" || status === "lost" || status === "timed_out") {
|
||||
return theme.error(padded);
|
||||
}
|
||||
if (status === "running") {
|
||||
return theme.accentBright(padded);
|
||||
}
|
||||
return theme.muted(padded);
|
||||
}
|
||||
|
||||
function formatTaskRows(tasks: TaskRecord[], rich: boolean) {
|
||||
const header = [
|
||||
"Task".padEnd(ID_PAD),
|
||||
"Kind".padEnd(RUNTIME_PAD),
|
||||
"Status".padEnd(STATUS_PAD),
|
||||
"Delivery".padEnd(DELIVERY_PAD),
|
||||
"Run".padEnd(RUN_PAD),
|
||||
"Child Session",
|
||||
"Summary",
|
||||
].join(" ");
|
||||
const lines = [rich ? theme.heading(header) : header];
|
||||
for (const task of tasks) {
|
||||
const summary = truncate(
|
||||
task.terminalSummary?.trim() ||
|
||||
task.progressSummary?.trim() ||
|
||||
task.label?.trim() ||
|
||||
task.task.trim(),
|
||||
80,
|
||||
);
|
||||
const line = [
|
||||
shortToken(task.taskId).padEnd(ID_PAD),
|
||||
task.runtime.padEnd(RUNTIME_PAD),
|
||||
formatTaskStatusCell(task.status, rich),
|
||||
task.deliveryStatus.padEnd(DELIVERY_PAD),
|
||||
shortToken(task.runId, RUN_PAD).padEnd(RUN_PAD),
|
||||
truncate(task.childSessionKey?.trim() || "n/a", 36).padEnd(36),
|
||||
summary,
|
||||
].join(" ");
|
||||
lines.push(line.trimEnd());
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
|
||||
function formatTaskListSummary(tasks: TaskRecord[]) {
|
||||
const summary = summarizeTaskRecords(tasks);
|
||||
return `${summary.byStatus.queued} queued · ${summary.byStatus.running} running · ${summary.failures} issues`;
|
||||
}
|
||||
|
||||
function formatAgeMs(ageMs: number | undefined): string {
|
||||
if (typeof ageMs !== "number" || ageMs < 1000) {
|
||||
return "fresh";
|
||||
}
|
||||
const totalSeconds = Math.floor(ageMs / 1000);
|
||||
const days = Math.floor(totalSeconds / 86_400);
|
||||
const hours = Math.floor((totalSeconds % 86_400) / 3600);
|
||||
const minutes = Math.floor((totalSeconds % 3600) / 60);
|
||||
if (days > 0) {
|
||||
return `${days}d${hours}h`;
|
||||
}
|
||||
if (hours > 0) {
|
||||
return `${hours}h${minutes}m`;
|
||||
}
|
||||
if (minutes > 0) {
|
||||
return `${minutes}m`;
|
||||
}
|
||||
return `${totalSeconds}s`;
|
||||
}
|
||||
|
||||
function formatAuditRows(findings: TaskAuditFinding[], rich: boolean) {
|
||||
const header = [
|
||||
"Severity".padEnd(8),
|
||||
"Code".padEnd(22),
|
||||
"Task".padEnd(ID_PAD),
|
||||
"Status".padEnd(STATUS_PAD),
|
||||
"Age".padEnd(8),
|
||||
"Detail",
|
||||
].join(" ");
|
||||
const lines = [rich ? theme.heading(header) : header];
|
||||
for (const finding of findings) {
|
||||
const severity = finding.severity.padEnd(8);
|
||||
const status = formatTaskStatusCell(finding.task.status, rich);
|
||||
const severityCell = !rich
|
||||
? severity
|
||||
: finding.severity === "error"
|
||||
? theme.error(severity)
|
||||
: theme.warn(severity);
|
||||
lines.push(
|
||||
[
|
||||
severityCell,
|
||||
finding.code.padEnd(22),
|
||||
shortToken(finding.task.taskId).padEnd(ID_PAD),
|
||||
status,
|
||||
formatAgeMs(finding.ageMs).padEnd(8),
|
||||
truncate(finding.detail, 88),
|
||||
]
|
||||
.join(" ")
|
||||
.trimEnd(),
|
||||
);
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
|
||||
export async function tasksListCommand(
|
||||
opts: { json?: boolean; runtime?: string; status?: string },
|
||||
runtime: RuntimeEnv,
|
||||
) {
|
||||
const runtimeFilter = opts.runtime?.trim();
|
||||
const statusFilter = opts.status?.trim();
|
||||
const tasks = reconcileInspectableTasks().filter((task) => {
|
||||
if (runtimeFilter && task.runtime !== runtimeFilter) {
|
||||
return false;
|
||||
}
|
||||
if (statusFilter && task.status !== statusFilter) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
if (opts.json) {
|
||||
runtime.log(
|
||||
JSON.stringify(
|
||||
{
|
||||
count: tasks.length,
|
||||
runtime: runtimeFilter ?? null,
|
||||
status: statusFilter ?? null,
|
||||
tasks,
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
runtime.log(info(`Background tasks: ${tasks.length}`));
|
||||
runtime.log(info(`Task pressure: ${formatTaskListSummary(tasks)}`));
|
||||
if (runtimeFilter) {
|
||||
runtime.log(info(`Runtime filter: ${runtimeFilter}`));
|
||||
}
|
||||
if (statusFilter) {
|
||||
runtime.log(info(`Status filter: ${statusFilter}`));
|
||||
}
|
||||
if (tasks.length === 0) {
|
||||
runtime.log("No background tasks found.");
|
||||
return;
|
||||
}
|
||||
const rich = isRich();
|
||||
for (const line of formatTaskRows(tasks, rich)) {
|
||||
runtime.log(line);
|
||||
}
|
||||
}
|
||||
|
||||
export async function tasksShowCommand(
|
||||
opts: { json?: boolean; lookup: string },
|
||||
runtime: RuntimeEnv,
|
||||
) {
|
||||
const task = reconcileTaskLookupToken(opts.lookup);
|
||||
if (!task) {
|
||||
runtime.error(`Task not found: ${opts.lookup}`);
|
||||
runtime.exit(1);
|
||||
return;
|
||||
}
|
||||
|
||||
if (opts.json) {
|
||||
runtime.log(JSON.stringify(task, null, 2));
|
||||
return;
|
||||
}
|
||||
|
||||
const lines = [
|
||||
"Background task:",
|
||||
`taskId: ${task.taskId}`,
|
||||
`kind: ${task.runtime}`,
|
||||
`sourceId: ${task.sourceId ?? "n/a"}`,
|
||||
`status: ${task.status}`,
|
||||
`result: ${task.terminalOutcome ?? "n/a"}`,
|
||||
`delivery: ${task.deliveryStatus}`,
|
||||
`notify: ${task.notifyPolicy}`,
|
||||
`requesterSessionKey: ${task.requesterSessionKey}`,
|
||||
`childSessionKey: ${task.childSessionKey ?? "n/a"}`,
|
||||
`parentTaskId: ${task.parentTaskId ?? "n/a"}`,
|
||||
`agentId: ${task.agentId ?? "n/a"}`,
|
||||
`runId: ${task.runId ?? "n/a"}`,
|
||||
`label: ${task.label ?? "n/a"}`,
|
||||
`task: ${task.task}`,
|
||||
`createdAt: ${new Date(task.createdAt).toISOString()}`,
|
||||
`startedAt: ${task.startedAt ? new Date(task.startedAt).toISOString() : "n/a"}`,
|
||||
`endedAt: ${task.endedAt ? new Date(task.endedAt).toISOString() : "n/a"}`,
|
||||
`lastEventAt: ${task.lastEventAt ? new Date(task.lastEventAt).toISOString() : "n/a"}`,
|
||||
`cleanupAfter: ${task.cleanupAfter ? new Date(task.cleanupAfter).toISOString() : "n/a"}`,
|
||||
...(task.error ? [`error: ${task.error}`] : []),
|
||||
...(task.progressSummary ? [`progressSummary: ${task.progressSummary}`] : []),
|
||||
...(task.terminalSummary ? [`terminalSummary: ${task.terminalSummary}`] : []),
|
||||
];
|
||||
for (const line of lines) {
|
||||
runtime.log(line);
|
||||
}
|
||||
}
|
||||
|
||||
export async function tasksNotifyCommand(
|
||||
opts: { lookup: string; notify: TaskNotifyPolicy },
|
||||
runtime: RuntimeEnv,
|
||||
) {
|
||||
const task = reconcileTaskLookupToken(opts.lookup);
|
||||
if (!task) {
|
||||
runtime.error(`Task not found: ${opts.lookup}`);
|
||||
runtime.exit(1);
|
||||
return;
|
||||
}
|
||||
const updated = updateTaskNotifyPolicyById({
|
||||
taskId: task.taskId,
|
||||
notifyPolicy: opts.notify,
|
||||
});
|
||||
if (!updated) {
|
||||
runtime.error(`Task not found: ${opts.lookup}`);
|
||||
runtime.exit(1);
|
||||
return;
|
||||
}
|
||||
runtime.log(`Updated ${updated.taskId} notify policy to ${updated.notifyPolicy}.`);
|
||||
}
|
||||
|
||||
export async function tasksCancelCommand(opts: { lookup: string }, runtime: RuntimeEnv) {
|
||||
const task = reconcileTaskLookupToken(opts.lookup);
|
||||
if (!task) {
|
||||
runtime.error(`Task not found: ${opts.lookup}`);
|
||||
runtime.exit(1);
|
||||
return;
|
||||
}
|
||||
const result = await cancelTaskById({
|
||||
cfg: loadConfig(),
|
||||
taskId: task.taskId,
|
||||
});
|
||||
if (!result.found) {
|
||||
runtime.error(result.reason ?? `Task not found: ${opts.lookup}`);
|
||||
runtime.exit(1);
|
||||
return;
|
||||
}
|
||||
if (!result.cancelled) {
|
||||
runtime.error(result.reason ?? `Could not cancel task: ${opts.lookup}`);
|
||||
runtime.exit(1);
|
||||
return;
|
||||
}
|
||||
const updated = getTaskById(task.taskId);
|
||||
runtime.log(
|
||||
`Cancelled ${updated?.taskId ?? task.taskId} (${updated?.runtime ?? task.runtime})${updated?.runId ? ` run ${updated.runId}` : ""}.`,
|
||||
);
|
||||
}
|
||||
|
||||
export async function tasksAuditCommand(
|
||||
opts: {
|
||||
json?: boolean;
|
||||
severity?: TaskAuditSeverity;
|
||||
code?: TaskAuditCode;
|
||||
limit?: number;
|
||||
},
|
||||
runtime: RuntimeEnv,
|
||||
) {
|
||||
const severityFilter = opts.severity?.trim() as TaskAuditSeverity | undefined;
|
||||
const codeFilter = opts.code?.trim() as TaskAuditCode | undefined;
|
||||
const allFindings = listTaskAuditFindings();
|
||||
const findings = allFindings.filter((finding) => {
|
||||
if (severityFilter && finding.severity !== severityFilter) {
|
||||
return false;
|
||||
}
|
||||
if (codeFilter && finding.code !== codeFilter) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
const limit = typeof opts.limit === "number" && opts.limit > 0 ? opts.limit : undefined;
|
||||
const displayed = limit ? findings.slice(0, limit) : findings;
|
||||
const summary = summarizeTaskAuditFindings(allFindings);
|
||||
|
||||
if (opts.json) {
|
||||
runtime.log(
|
||||
JSON.stringify(
|
||||
{
|
||||
count: allFindings.length,
|
||||
filteredCount: findings.length,
|
||||
displayed: displayed.length,
|
||||
filters: {
|
||||
severity: severityFilter ?? null,
|
||||
code: codeFilter ?? null,
|
||||
limit: limit ?? null,
|
||||
},
|
||||
summary,
|
||||
findings: displayed,
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
runtime.log(
|
||||
info(
|
||||
`Task audit: ${summary.total} findings · ${summary.errors} errors · ${summary.warnings} warnings`,
|
||||
),
|
||||
);
|
||||
if (severityFilter || codeFilter) {
|
||||
runtime.log(info(`Showing ${findings.length} matching findings.`));
|
||||
}
|
||||
if (severityFilter) {
|
||||
runtime.log(info(`Severity filter: ${severityFilter}`));
|
||||
}
|
||||
if (codeFilter) {
|
||||
runtime.log(info(`Code filter: ${codeFilter}`));
|
||||
}
|
||||
if (limit) {
|
||||
runtime.log(info(`Limit: ${limit}`));
|
||||
}
|
||||
if (displayed.length === 0) {
|
||||
runtime.log("No task audit findings.");
|
||||
return;
|
||||
}
|
||||
const rich = isRich();
|
||||
for (const line of formatAuditRows(displayed, rich)) {
|
||||
runtime.log(line);
|
||||
}
|
||||
}
|
||||
|
||||
export async function tasksMaintenanceCommand(
|
||||
opts: { json?: boolean; apply?: boolean },
|
||||
runtime: RuntimeEnv,
|
||||
) {
|
||||
const auditBefore = getInspectableTaskAuditSummary();
|
||||
const maintenance = opts.apply ? runTaskRegistryMaintenance() : previewTaskRegistryMaintenance();
|
||||
const summary = getInspectableTaskRegistrySummary();
|
||||
const auditAfter = opts.apply ? getInspectableTaskAuditSummary() : auditBefore;
|
||||
|
||||
if (opts.json) {
|
||||
runtime.log(
|
||||
JSON.stringify(
|
||||
{
|
||||
mode: opts.apply ? "apply" : "preview",
|
||||
maintenance,
|
||||
tasks: summary,
|
||||
auditBefore,
|
||||
auditAfter,
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
runtime.log(
|
||||
info(
|
||||
`Task maintenance (${opts.apply ? "applied" : "preview"}): ${maintenance.reconciled} reconcile · ${maintenance.cleanupStamped} cleanup stamp · ${maintenance.pruned} prune`,
|
||||
),
|
||||
);
|
||||
runtime.log(
|
||||
info(
|
||||
`${opts.apply ? "Task health after apply" : "Task health"}: ${summary.byStatus.queued} queued · ${summary.byStatus.running} running · ${auditAfter.errors} audit errors · ${auditAfter.warnings} audit warnings`,
|
||||
),
|
||||
);
|
||||
if (opts.apply) {
|
||||
runtime.log(
|
||||
info(
|
||||
`Task health before apply: ${auditBefore.errors} audit errors · ${auditBefore.warnings} audit warnings`,
|
||||
),
|
||||
);
|
||||
}
|
||||
if (!opts.apply) {
|
||||
runtime.log("Dry run only. Re-run with `openclaw tasks maintenance --apply` to write changes.");
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import * as taskExecutor from "openclaw/plugin-sdk/tasks";
|
||||
import { findTaskByRunId, resetTaskRegistryForTests } from "openclaw/plugin-sdk/tasks";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import * as taskExecutor from "../../tasks/task-executor.js";
|
||||
import { findTaskByRunId, resetTaskRegistryForTests } from "../../tasks/task-registry.js";
|
||||
import { setupCronServiceSuite, writeCronStoreSnapshot } from "../service.test-harness.js";
|
||||
import type { CronJob } from "../types.js";
|
||||
import { run, start, stop } from "./ops.js";
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import { enqueueCommandInLane } from "../../process/command-queue.js";
|
||||
import { CommandLane } from "../../process/lanes.js";
|
||||
import {
|
||||
completeTaskRunByRunId,
|
||||
createRunningTaskRun,
|
||||
failTaskRunByRunId,
|
||||
} from "openclaw/plugin-sdk/tasks";
|
||||
import { enqueueCommandInLane } from "../../process/command-queue.js";
|
||||
import { CommandLane } from "../../process/lanes.js";
|
||||
} from "../../tasks/task-executor.js";
|
||||
import type { CronJob, CronJobCreate, CronJobPatch } from "../types.js";
|
||||
import { normalizeCronCreateDeliveryInput } from "./initial-delivery.js";
|
||||
import {
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import fs from "node:fs/promises";
|
||||
import * as taskExecutor from "openclaw/plugin-sdk/tasks";
|
||||
import { resetTaskRegistryForTests } from "openclaw/plugin-sdk/tasks";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { setupCronServiceSuite, writeCronStoreSnapshot } from "../../cron/service.test-harness.js";
|
||||
import { createCronServiceState } from "../../cron/service/state.js";
|
||||
import { onTimer } from "../../cron/service/timer.js";
|
||||
import type { CronJob } from "../../cron/types.js";
|
||||
import * as taskExecutor from "../../tasks/task-executor.js";
|
||||
import { resetTaskRegistryForTests } from "../../tasks/task-registry.js";
|
||||
|
||||
const { logger, makeStorePath } = setupCronServiceSuite({
|
||||
prefix: "cron-service-timer-seam",
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
import {
|
||||
completeTaskRunByRunId,
|
||||
createRunningTaskRun,
|
||||
failTaskRunByRunId,
|
||||
} from "openclaw/plugin-sdk/tasks";
|
||||
import { resolveFailoverReasonFromError } from "../../agents/failover-error.js";
|
||||
import type { CronConfig, CronRetryOn } from "../../config/types.cron.js";
|
||||
import type { HeartbeatRunResult } from "../../infra/heartbeat-wake.js";
|
||||
import { DEFAULT_AGENT_ID } from "../../routing/session-key.js";
|
||||
import {
|
||||
completeTaskRunByRunId,
|
||||
createRunningTaskRun,
|
||||
failTaskRunByRunId,
|
||||
} from "../../tasks/task-executor.js";
|
||||
import { resolveCronDeliveryPlan } from "../delivery.js";
|
||||
import { sweepCronRunSessions } from "../session-reaper.js";
|
||||
import type {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { findTaskByRunId, resetTaskRegistryForTests } from "openclaw/plugin-sdk/tasks";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { BARE_SESSION_RESET_PROMPT } from "../../auto-reply/reply/session-reset-prompt.js";
|
||||
import { findTaskByRunId, resetTaskRegistryForTests } from "../../tasks/task-registry.js";
|
||||
import { withTempDir } from "../../test-helpers/temp-dir.js";
|
||||
import { agentHandlers } from "./agent.js";
|
||||
import { expectSubagentFollowupReactivation } from "./subagent-followup.test-helpers.js";
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import { randomUUID } from "node:crypto";
|
||||
import { createRunningTaskRun } from "openclaw/plugin-sdk/tasks";
|
||||
import { listAgentIds } from "../../agents/agent-scope.js";
|
||||
import type { AgentInternalEvent } from "../../agents/internal-events.js";
|
||||
import {
|
||||
|
|
@ -29,6 +28,7 @@ import { classifySessionKeyShape, normalizeAgentId } from "../../routing/session
|
|||
import { defaultRuntime } from "../../runtime.js";
|
||||
import { normalizeInputProvenance, type InputProvenance } from "../../sessions/input-provenance.js";
|
||||
import { resolveSendPolicy } from "../../sessions/send-policy.js";
|
||||
import { createRunningTaskRun } from "../../tasks/task-executor.js";
|
||||
import {
|
||||
normalizeDeliveryContext,
|
||||
normalizeSessionDeliveryFields,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import { getInspectableTaskRegistrySummary } from "openclaw/plugin-sdk/tasks-summary";
|
||||
import { getActiveEmbeddedRunCount } from "../agents/pi-embedded-runner/runs.js";
|
||||
import { getTotalPendingReplies } from "../auto-reply/reply/dispatcher-registry.js";
|
||||
import type { CliDeps } from "../cli/deps.js";
|
||||
|
|
@ -17,6 +16,7 @@ import {
|
|||
} from "../infra/restart.js";
|
||||
import { setCommandLaneConcurrency, getTotalQueueSize } from "../process/command-queue.js";
|
||||
import { CommandLane } from "../process/lanes.js";
|
||||
import { getInspectableTaskRegistrySummary } from "../tasks/task-registry.maintenance.js";
|
||||
import type { ChannelHealthMonitor } from "./channel-health-monitor.js";
|
||||
import type { ChannelKind } from "./config-reload-plan.js";
|
||||
import type { GatewayReloadPlan } from "./config-reload.js";
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import path from "node:path";
|
||||
import { getInspectableTaskRegistrySummary } from "openclaw/plugin-sdk/tasks-summary";
|
||||
import { resolveAgentWorkspaceDir, resolveDefaultAgentId } from "../agents/agent-scope.js";
|
||||
import { getActiveEmbeddedRunCount } from "../agents/pi-embedded-runner/runs.js";
|
||||
import { registerSkillsChangeListener } from "../agents/skills/refresh.js";
|
||||
|
|
@ -79,6 +78,10 @@ import {
|
|||
} from "../secrets/runtime.js";
|
||||
import { onSessionLifecycleEvent } from "../sessions/session-lifecycle-events.js";
|
||||
import { onSessionTranscriptUpdate } from "../sessions/transcript-events.js";
|
||||
import {
|
||||
getInspectableTaskRegistrySummary,
|
||||
startTaskRegistryMaintenance,
|
||||
} from "../tasks/task-registry.maintenance.js";
|
||||
import { runSetupWizard } from "../wizard/setup.js";
|
||||
import { createAuthRateLimiter, type AuthRateLimiter } from "./auth-rate-limit.js";
|
||||
import { startChannelHealthMonitor } from "./channel-health-monitor.js";
|
||||
|
|
@ -905,6 +908,7 @@ export async function startGatewayServer(
|
|||
});
|
||||
|
||||
if (!minimalTestGateway) {
|
||||
startTaskRegistryMaintenance();
|
||||
({ tickInterval, healthInterval, dedupeCleanup, mediaCleanup } =
|
||||
startGatewayMaintenanceTimers({
|
||||
broadcast,
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
import { defaultTaskOperationsRuntime } from "../../packages/tasks-host-sdk/src/runtime-core.js";
|
||||
import { startTaskRegistryMaintenance } from "../../packages/tasks-host-sdk/src/runtime-core.js";
|
||||
import type { OpenClawPluginService } from "../plugins/types.js";
|
||||
|
||||
export * from "../../packages/tasks-host-sdk/src/runtime-core.js";
|
||||
import { defaultTaskOperationsRuntime } from "../tasks/operations-runtime.js";
|
||||
import { startTaskRegistryMaintenance } from "../tasks/task-registry.maintenance.js";
|
||||
|
||||
export const defaultOperationsRuntime = defaultTaskOperationsRuntime;
|
||||
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
import {
|
||||
createEmptyTaskAuditSummary,
|
||||
type TaskAuditSummary,
|
||||
} from "../../packages/tasks-host-sdk/src/task-registry.audit.shared.js";
|
||||
import { createEmptyTaskRegistrySummary } from "../../packages/tasks-host-sdk/src/task-registry.summary.js";
|
||||
import type { TaskRegistrySummary } from "../../packages/tasks-host-sdk/src/task-registry.types.js";
|
||||
|
||||
export { createEmptyTaskAuditSummary, createEmptyTaskRegistrySummary };
|
||||
|
||||
export type { TaskAuditSummary, TaskRegistrySummary };
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
import {
|
||||
createEmptyTaskAuditSummary,
|
||||
type TaskAuditSummary,
|
||||
} from "../../packages/tasks-host-sdk/src/task-registry.audit.shared.js";
|
||||
import {
|
||||
getInspectableTaskAuditSummary,
|
||||
getInspectableTaskRegistrySummary,
|
||||
} from "../../packages/tasks-host-sdk/src/task-registry.maintenance.js";
|
||||
import { createEmptyTaskRegistrySummary } from "../../packages/tasks-host-sdk/src/task-registry.summary.js";
|
||||
import type { TaskRegistrySummary } from "../../packages/tasks-host-sdk/src/task-registry.types.js";
|
||||
|
||||
export {
|
||||
createEmptyTaskAuditSummary,
|
||||
createEmptyTaskRegistrySummary,
|
||||
getInspectableTaskAuditSummary,
|
||||
getInspectableTaskRegistrySummary,
|
||||
};
|
||||
|
||||
export type { TaskAuditSummary, TaskRegistrySummary };
|
||||
|
|
@ -1,4 +1,3 @@
|
|||
import { defaultTaskOperationsRuntime } from "openclaw/plugin-sdk/tasks";
|
||||
import { resolveStateDir } from "../../config/paths.js";
|
||||
import { loadBundledPluginPublicSurfaceModuleSync } from "../../plugin-sdk/facade-runtime.js";
|
||||
import { resolveGlobalSingleton } from "../../shared/global-singleton.js";
|
||||
|
|
@ -7,6 +6,7 @@ import {
|
|||
createLazyRuntimeMethodBinder,
|
||||
createLazyRuntimeModule,
|
||||
} from "../../shared/lazy-runtime.js";
|
||||
import { defaultTaskOperationsRuntime } from "../../tasks/operations-runtime.js";
|
||||
import { VERSION } from "../../version.js";
|
||||
import { listWebSearchProviders, runWebSearch } from "../../web-search/runtime.js";
|
||||
import { getRegisteredOperationsRuntime } from "../operations-state.js";
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { chmodSync, existsSync, mkdirSync } from "node:fs";
|
||||
import type { DatabaseSync, StatementSync } from "node:sqlite";
|
||||
import { requireNodeSqlite } from "../../../src/infra/node-sqlite.js";
|
||||
import type { DeliveryContext } from "../../../src/utils/delivery-context.js";
|
||||
import { requireNodeSqlite } from "../infra/node-sqlite.js";
|
||||
import type { DeliveryContext } from "../utils/delivery-context.js";
|
||||
import { resolveFlowRegistryDir, resolveFlowRegistrySqlitePath } from "./flow-registry.paths.js";
|
||||
import type { FlowRegistryStoreSnapshot } from "./flow-registry.store.js";
|
||||
import type { FlowOutputBag, FlowRecord, FlowShape } from "./flow-registry.types.js";
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import { statSync } from "node:fs";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { withTempDir } from "../../../src/test-helpers/temp-dir.js";
|
||||
import { withTempDir } from "../test-helpers/temp-dir.js";
|
||||
import { createFlowRecord, getFlowById, resetFlowRegistryForTests } from "./flow-registry.js";
|
||||
import { resolveFlowRegistryDir, resolveFlowRegistrySqlitePath } from "./flow-registry.paths.js";
|
||||
import { configureFlowRegistryRuntime } from "./flow-registry.store.js";
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { withTempDir } from "../../../src/test-helpers/temp-dir.js";
|
||||
import { withTempDir } from "../test-helpers/temp-dir.js";
|
||||
import {
|
||||
createFlowRecord,
|
||||
deleteFlowRecordById,
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import type { DeliveryContext } from "../../../src/utils/delivery-context.js";
|
||||
import type { DeliveryContext } from "../utils/delivery-context.js";
|
||||
import type { TaskNotifyPolicy } from "./task-registry.types.js";
|
||||
|
||||
export type FlowShape = "single_task" | "linear";
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { withTempDir } from "../../../src/test-helpers/temp-dir.js";
|
||||
import { withTempDir } from "../test-helpers/temp-dir.js";
|
||||
import { getFlowById, resetFlowRegistryForTests, updateFlowRecordById } from "./flow-registry.js";
|
||||
import {
|
||||
appendFlowOutput,
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import { requestHeartbeatNow } from "../../../src/infra/heartbeat-wake.js";
|
||||
import { enqueueSystemEvent } from "../../../src/infra/system-events.js";
|
||||
import { parseAgentSessionKey } from "../../../src/routing/session-key.js";
|
||||
import { isDeliverableMessageChannel } from "../../../src/utils/message-channel.js";
|
||||
import { requestHeartbeatNow } from "../infra/heartbeat-wake.js";
|
||||
import { enqueueSystemEvent } from "../infra/system-events.js";
|
||||
import { parseAgentSessionKey } from "../routing/session-key.js";
|
||||
import { isDeliverableMessageChannel } from "../utils/message-channel.js";
|
||||
import { createFlowRecord, getFlowById, updateFlowRecordById } from "./flow-registry.js";
|
||||
import type { FlowOutputBag, FlowOutputValue, FlowRecord } from "./flow-registry.types.js";
|
||||
import { createQueuedTaskRun, createRunningTaskRun } from "./task-executor.js";
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import { withTempDir } from "../../../src/test-helpers/temp-dir.js";
|
||||
import { withTempDir } from "../test-helpers/temp-dir.js";
|
||||
import { defaultTaskOperationsRuntime } from "./operations-runtime.js";
|
||||
import { findTaskByRunId, resetTaskRegistryForTests } from "./task-registry.js";
|
||||
|
||||
|
|
@ -10,8 +10,8 @@ import type {
|
|||
PluginOperationSummary,
|
||||
PluginOperationsCancelResult,
|
||||
PluginOperationsRuntime,
|
||||
} from "../../../src/plugins/operations-state.js";
|
||||
import { summarizeOperationRecords } from "../../../src/plugins/operations-state.js";
|
||||
} from "../plugins/operations-state.js";
|
||||
import { summarizeOperationRecords } from "../plugins/operations-state.js";
|
||||
import {
|
||||
listTaskAuditFindings,
|
||||
type TaskAuditFinding,
|
||||
|
|
@ -14,10 +14,10 @@ const RAW_TASK_MUTATORS = [
|
|||
] as const;
|
||||
|
||||
const ALLOWED_CALLERS = new Set([
|
||||
"src/operations-runtime.ts",
|
||||
"src/task-executor.ts",
|
||||
"src/task-registry.ts",
|
||||
"src/task-registry.maintenance.ts",
|
||||
"tasks/operations-runtime.ts",
|
||||
"tasks/task-executor.ts",
|
||||
"tasks/task-registry.ts",
|
||||
"tasks/task-registry.maintenance.ts",
|
||||
]);
|
||||
|
||||
async function listSourceFiles(root: string): Promise<string[]> {
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { withTempDir } from "../../../src/test-helpers/temp-dir.js";
|
||||
import { withTempDir } from "../test-helpers/temp-dir.js";
|
||||
import {
|
||||
getFlowById,
|
||||
listFlowRecords,
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import type { OpenClawConfig } from "../../../src/config/config.js";
|
||||
import { createSubsystemLogger } from "../../../src/logging/subsystem.js";
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import { createSubsystemLogger } from "../logging/subsystem.js";
|
||||
import {
|
||||
createFlowForTask,
|
||||
createFlowRecord,
|
||||
|
|
@ -0,0 +1 @@
|
|||
export { sendMessage } from "../infra/outbound/message.js";
|
||||
|
|
@ -6,11 +6,15 @@ const TASK_ROOT = path.resolve(import.meta.dirname);
|
|||
const SRC_ROOT = path.resolve(TASK_ROOT, "..");
|
||||
|
||||
const ALLOWED_IMPORTERS = new Set([
|
||||
"src/flow-runtime.ts",
|
||||
"src/operations-runtime.ts",
|
||||
"src/runtime-core.ts",
|
||||
"src/task-executor.ts",
|
||||
"src/task-registry.maintenance.ts",
|
||||
"agents/tools/session-status-tool.ts",
|
||||
"auto-reply/reply/commands-acp/runtime-options.ts",
|
||||
"auto-reply/reply/commands-subagents/action-info.ts",
|
||||
"commands/doctor-workspace-status.ts",
|
||||
"commands/flows.ts",
|
||||
"tasks/flow-runtime.ts",
|
||||
"tasks/operations-runtime.ts",
|
||||
"tasks/task-executor.ts",
|
||||
"tasks/task-registry.maintenance.ts",
|
||||
]);
|
||||
|
||||
async function listSourceFiles(root: string): Promise<string[]> {
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import { readAcpSessionEntry } from "../../../src/acp/runtime/session-meta.js";
|
||||
import { loadSessionStore, resolveStorePath } from "../../../src/config/sessions.js";
|
||||
import { parseAgentSessionKey } from "../../../src/routing/session-key.js";
|
||||
import { readAcpSessionEntry } from "../acp/runtime/session-meta.js";
|
||||
import { loadSessionStore, resolveStorePath } from "../config/sessions.js";
|
||||
import { parseAgentSessionKey } from "../routing/session-key.js";
|
||||
import { listTaskAuditFindings, summarizeTaskAuditFindings } from "./task-registry.audit.js";
|
||||
import type { TaskAuditSummary } from "./task-registry.audit.js";
|
||||
import {
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { resolveStateDir } from "../../../src/config/paths.js";
|
||||
import { resolveStateDir } from "../config/paths.js";
|
||||
|
||||
export function resolveTaskStateDir(env: NodeJS.ProcessEnv = process.env): string {
|
||||
const explicit = env.OPENCLAW_STATE_DIR?.trim();
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import { chmodSync, existsSync, mkdirSync } from "node:fs";
|
||||
import type { DatabaseSync, StatementSync } from "node:sqlite";
|
||||
import { requireNodeSqlite } from "../../../src/infra/node-sqlite.js";
|
||||
import type { DeliveryContext } from "../../../src/utils/delivery-context.js";
|
||||
import { requireNodeSqlite } from "../infra/node-sqlite.js";
|
||||
import type { DeliveryContext } from "../utils/delivery-context.js";
|
||||
import { resolveTaskRegistryDir, resolveTaskRegistrySqlitePath } from "./task-registry.paths.js";
|
||||
import type { TaskRegistryStoreSnapshot } from "./task-registry.store.js";
|
||||
import type { TaskDeliveryState, TaskRecord } from "./task-registry.types.js";
|
||||
|
|
@ -1,13 +1,13 @@
|
|||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { startAcpSpawnParentStreamRelay } from "../../../src/agents/acp-spawn-parent-stream.js";
|
||||
import { emitAgentEvent } from "../../../src/infra/agent-events.js";
|
||||
import { startAcpSpawnParentStreamRelay } from "../agents/acp-spawn-parent-stream.js";
|
||||
import { emitAgentEvent } from "../infra/agent-events.js";
|
||||
import {
|
||||
hasPendingHeartbeatWake,
|
||||
resetHeartbeatWakeStateForTests,
|
||||
} from "../../../src/infra/heartbeat-wake.js";
|
||||
import { peekSystemEvents, resetSystemEventsForTest } from "../../../src/infra/system-events.js";
|
||||
import { withTempDir } from "../../../src/test-helpers/temp-dir.js";
|
||||
import { installInMemoryTaskAndFlowRegistryRuntime } from "../../../src/test-utils/task-flow-registry-runtime.js";
|
||||
} from "../infra/heartbeat-wake.js";
|
||||
import { peekSystemEvents, resetSystemEventsForTest } from "../infra/system-events.js";
|
||||
import { withTempDir } from "../test-helpers/temp-dir.js";
|
||||
import { installInMemoryTaskAndFlowRegistryRuntime } from "../test-utils/task-flow-registry-runtime.js";
|
||||
import { createFlowRecord, getFlowById, resetFlowRegistryForTests } from "./flow-registry.js";
|
||||
import {
|
||||
createTaskRecord,
|
||||
|
|
@ -1,14 +1,14 @@
|
|||
import crypto from "node:crypto";
|
||||
import { getAcpSessionManager } from "../../../src/acp/control-plane/manager.js";
|
||||
import { killSubagentRunAdmin } from "../../../src/agents/subagent-control.js";
|
||||
import type { OpenClawConfig } from "../../../src/config/config.js";
|
||||
import { onAgentEvent } from "../../../src/infra/agent-events.js";
|
||||
import { requestHeartbeatNow } from "../../../src/infra/heartbeat-wake.js";
|
||||
import { enqueueSystemEvent } from "../../../src/infra/system-events.js";
|
||||
import { createSubsystemLogger } from "../../../src/logging/subsystem.js";
|
||||
import { parseAgentSessionKey } from "../../../src/routing/session-key.js";
|
||||
import { normalizeDeliveryContext } from "../../../src/utils/delivery-context.js";
|
||||
import { isDeliverableMessageChannel } from "../../../src/utils/message-channel.js";
|
||||
import { getAcpSessionManager } from "../acp/control-plane/manager.js";
|
||||
import { killSubagentRunAdmin } from "../agents/subagent-control.js";
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import { onAgentEvent } from "../infra/agent-events.js";
|
||||
import { requestHeartbeatNow } from "../infra/heartbeat-wake.js";
|
||||
import { enqueueSystemEvent } from "../infra/system-events.js";
|
||||
import { createSubsystemLogger } from "../logging/subsystem.js";
|
||||
import { parseAgentSessionKey } from "../routing/session-key.js";
|
||||
import { normalizeDeliveryContext } from "../utils/delivery-context.js";
|
||||
import { isDeliverableMessageChannel } from "../utils/message-channel.js";
|
||||
import { getFlowById, syncFlowFromTask } from "./flow-registry.js";
|
||||
import {
|
||||
formatTaskBlockedFollowupMessage,
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import type { DeliveryContext } from "../../../src/utils/delivery-context.js";
|
||||
import type { DeliveryContext } from "../utils/delivery-context.js";
|
||||
|
||||
export type TaskRuntime = "subagent" | "acp" | "cli" | "cron";
|
||||
|
||||
|
|
@ -2,14 +2,14 @@ import {
|
|||
configureFlowRegistryRuntime,
|
||||
type FlowRegistryStore,
|
||||
type FlowRegistryStoreSnapshot,
|
||||
} from "openclaw/plugin-sdk/tasks";
|
||||
import type { FlowRecord } from "openclaw/plugin-sdk/tasks";
|
||||
} from "../tasks/flow-registry.store.js";
|
||||
import type { FlowRecord } from "../tasks/flow-registry.types.js";
|
||||
import {
|
||||
configureTaskRegistryRuntime,
|
||||
type TaskRegistryStore,
|
||||
type TaskRegistryStoreSnapshot,
|
||||
} from "openclaw/plugin-sdk/tasks";
|
||||
import type { TaskDeliveryState, TaskRecord } from "openclaw/plugin-sdk/tasks";
|
||||
} from "../tasks/task-registry.store.js";
|
||||
import type { TaskDeliveryState, TaskRecord } from "../tasks/task-registry.types.js";
|
||||
|
||||
function cloneTask(task: TaskRecord): TaskRecord {
|
||||
return { ...task };
|
||||
|
|
|
|||
|
|
@ -13,8 +13,7 @@
|
|||
"include": [
|
||||
"src/plugin-sdk/**/*.ts",
|
||||
"src/types/**/*.d.ts",
|
||||
"packages/memory-host-sdk/src/**/*.ts",
|
||||
"packages/tasks-host-sdk/src/**/*.ts"
|
||||
"packages/memory-host-sdk/src/**/*.ts"
|
||||
],
|
||||
"exclude": ["node_modules", "dist", "src/**/*.test.ts"]
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue