File size: 3,331 Bytes
d197cf3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
import { createHash } from "node:crypto";
import { writeFile } from "node:fs/promises";
import { join } from "node:path";
import { hasActiveCronJobsForAgent } from "../cron/active-jobs.js";
import { getSuspensionVisibleCronTaskRunCount } from "../cron/service/active-run-cancellation.js";
import { hasPendingCronSessionCleanupForAgent } from "../cron/service/locked.js";
import { buildClawAddPlan } from "./lifecycle.js";
import type { ClawMonitorCleanupGateway } from "./monitor-cleanup-contract.js";
import { parseClawManifest } from "./schema.js";
import type { ClawSourceIdentity } from "./types.js";

async function verifyQuiescentFixture(agentId: string): Promise<void> {
  if (
    hasActiveCronJobsForAgent(agentId) ||
    getSuspensionVisibleCronTaskRunCount({ agentId }) > 0 ||
    hasPendingCronSessionCleanupForAgent(agentId)
  ) {
    throw new Error("Claw unit fixture unexpectedly owns pending scheduled work.");
  }
}

// These Claw unit fixtures have no scheduler or workers. The serving-owner integration
// suite proves cancellation, actual settlement, persistence, and lifecycle fencing.
export const quiescentClawMonitorGateway: ClawMonitorCleanupGateway = {
  inspect: async () => [],
  quiesce: verifyQuiescentFixture,
  drain: verifyQuiescentFixture,
};

export async function buildClawRemovalFixture(
  root: string,
  params: {
    id?: string;
    name?: string;
    withFile?: boolean;
    withBootstrap?: boolean;
    withCron?: boolean;
    withMcp?: boolean;
  } = {},
) {
  if (params.withFile) {
    await writeFile(join(root, "SOUL.md"), "managed\n", "utf8");
  }
  if (params.withBootstrap) {
    await writeFile(join(root, "BOOTSTRAP.md"), "managed\n", "utf8");
  }
  const parsed = parseClawManifest({
    schemaVersion: 1,
    agent: { id: params.id ?? "worker", name: "Worker" },
    workspace: params.withFile ? { bootstrapFiles: { "SOUL.md": { source: "SOUL.md" } } } : {},
    mcpServers: params.withMcp
      ? {
          docs: {
            command: "uvx",
            args: ["docs-mcp"],
            env: { DOCS_TOKEN: "${DOCS_TOKEN}" },
          },
        }
      : {},
    cronJobs: params.withCron
      ? [
          {
            id: "daily-report",
            schedule: { cron: "0 9 * * *", timezone: "UTC" },
            session: "isolated",
            message: "Prepare report",
          },
        ]
      : [],
  });
  if (!parsed.ok) {
    throw new Error(JSON.stringify(parsed.diagnostics));
  }
  const source: ClawSourceIdentity = {
    kind: "package",
    name: params.name ?? "@acme/worker",
    version: "1.0.0",
    packageRoot: root,
    manifestPath: join(root, "openclaw.claw.json"),
    integrityKind: "artifact",
    integrity: "sha256:manifest",
    byteLength: 100,
  };
  const plan = await buildClawAddPlan({
    manifest: parsed.manifest,
    ...(params.withBootstrap
      ? {
          packageBootstrap: {
            sourcePath: "BOOTSTRAP.md",
            realPath: join(root, "BOOTSTRAP.md"),
            byteLength: Buffer.byteLength("managed\n"),
            digest: `sha256:${createHash("sha256").update("managed\n").digest("hex")}`,
          },
        }
      : {}),
    source,
    context: { workspace: join(root, `workspace-${params.id ?? "worker"}`) },
  });
  return { root, plan, env: { OPENCLAW_STATE_DIR: join(root, "state") } };
}