refactor: remove context awareness documentation and related code; streamline tool registration and context detection

This commit is contained in:
Lauren ten Hoor
2026-02-12 00:25:34 +08:00
parent dc3a7fcf9e
commit e4b54646da
15 changed files with 51 additions and 488 deletions

View File

@@ -2,17 +2,15 @@
* health — Worker health scan with optional auto-fix.
*
* Read-only by default (surfaces issues). Pass fix=true to apply fixes.
* Context-aware: auto-filters to project in group chats.
*/
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
import { jsonResult } from "openclaw/plugin-sdk";
import type { ToolContext } from "../types.js";
import { readProjects, getProject, type Project } from "../projects.js";
import { readProjects, getProject } from "../projects.js";
import { log as auditLog } from "../audit.js";
import { checkWorkerHealth, type HealthFix } from "../services/health.js";
import { requireWorkspaceDir, resolveContext, resolveProvider } from "../tool-helpers.js";
import { requireWorkspaceDir, resolveProvider } from "../tool-helpers.js";
export function createHealthTool(api: OpenClawPluginApi) {
export function createHealthTool() {
return (ctx: ToolContext) => ({
name: "health",
label: "Health",
@@ -31,10 +29,7 @@ export function createHealthTool(api: OpenClawPluginApi) {
const fix = (params.fix as boolean) ?? false;
const activeSessions = (params.activeSessions as string[]) ?? [];
// Auto-filter in group context
const context = await resolveContext(ctx, api);
let groupId = params.projectGroupId as string | undefined;
if (context.type === "group" && !groupId) groupId = context.groupId;
const groupId = params.projectGroupId as string | undefined;
const data = await readProjects(workspaceDir);
const projectIds = groupId ? [groupId] : Object.keys(data.projects);

View File

@@ -7,7 +7,6 @@ import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
import { jsonResult } from "openclaw/plugin-sdk";
import type { ToolContext } from "../types.js";
import { isPluginConfigured, hasWorkspaceFiles, buildOnboardToolContext, buildReconfigContext } from "../onboarding.js";
import { detectContext, generateGuardrails } from "../context-guard.js";
export function createOnboardTool(api: OpenClawPluginApi) {
return (ctx: ToolContext) => ({
@@ -22,17 +21,6 @@ export function createOnboardTool(api: OpenClawPluginApi) {
},
async execute(_id: string, params: Record<string, unknown>) {
const devClawAgentIds = ((api.pluginConfig as Record<string, unknown>)?.devClawAgentIds as string[] | undefined) ?? [];
const context = await detectContext(ctx, devClawAgentIds);
if (context.type === "group") {
return jsonResult({
success: false, error: "Onboarding should not be done in group chats.",
recommendation: "Use a direct message instead.",
contextGuidance: generateGuardrails(context),
});
}
const configured = isPluginConfigured(api.pluginConfig as Record<string, unknown>);
const hasWorkspace = await hasWorkspaceFiles(ctx.workspaceDir);
const mode = params.mode ? (params.mode as "first-run" | "reconfigure")
@@ -42,7 +30,6 @@ export function createOnboardTool(api: OpenClawPluginApi) {
return jsonResult({
success: true, mode, configured, instructions,
contextGuidance: generateGuardrails(context),
nextSteps: ["Follow instructions above", "Call setup with collected answers", mode === "first-run" ? "Register a project afterward" : null].filter(Boolean),
});
},

View File

@@ -6,7 +6,6 @@
*
* Replaces the manual steps of running glab/gh label create + editing projects.json.
*/
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
import { jsonResult } from "openclaw/plugin-sdk";
import type { ToolContext } from "../types.js";
import fs from "node:fs/promises";
@@ -17,7 +16,6 @@ import { createProvider } from "../providers/index.js";
import { log as auditLog } from "../audit.js";
import { DEV_LEVELS, QA_LEVELS } from "../tiers.js";
import { DEFAULT_DEV_INSTRUCTIONS, DEFAULT_QA_INSTRUCTIONS } from "../templates.js";
import { detectContext, generateGuardrails } from "../context-guard.js";
/**
* Scaffold project-specific prompt files.
@@ -48,18 +46,18 @@ async function scaffoldPromptFiles(workspaceDir: string, projectName: string): P
return created;
}
export function createProjectRegisterTool(api: OpenClawPluginApi) {
export function createProjectRegisterTool() {
return (ctx: ToolContext) => ({
name: "project_register",
label: "Project Register",
description: `Register a new project with DevClaw. ONLY works in the Telegram/WhatsApp group you're registering. Creates state labels, adds to projects.json, auto-populates group ID. One-time setup per project.`,
description: `Register a new project with DevClaw. Creates state labels, adds to projects.json. One-time setup per project.`,
parameters: {
type: "object",
required: ["name", "repo", "baseBranch"],
required: ["projectGroupId", "name", "repo", "baseBranch"],
properties: {
projectGroupId: {
type: "string",
description: "Telegram/WhatsApp group ID (optional - auto-detected from current group if omitted)",
description: "Project group ID (e.g. Telegram/WhatsApp group ID)",
},
name: {
type: "string",
@@ -69,6 +67,10 @@ export function createProjectRegisterTool(api: OpenClawPluginApi) {
type: "string",
description: "Path to git repo (e.g. '~/git/my-project')",
},
channel: {
type: "string",
description: "Channel type (e.g. 'telegram', 'whatsapp'). Defaults to 'telegram'.",
},
groupName: {
type: "string",
description: "Group display name (optional - defaults to 'Project: {name}')",
@@ -97,6 +99,7 @@ export function createProjectRegisterTool(api: OpenClawPluginApi) {
const groupId = params.projectGroupId as string;
const name = params.name as string;
const repo = params.repo as string;
const channel = (params.channel as string) ?? "telegram";
const groupName = (params.groupName as string) ?? `Project: ${name}`;
const baseBranch = params.baseBranch as string;
const deployBranch = (params.deployBranch as string) ?? baseBranch;
@@ -108,43 +111,9 @@ export function createProjectRegisterTool(api: OpenClawPluginApi) {
throw new Error("No workspace directory available in tool context");
}
// --- Context detection ---
const devClawAgentIds =
((api.pluginConfig as Record<string, unknown>)?.devClawAgentIds as
| string[]
| undefined) ?? [];
const context = await detectContext(ctx, devClawAgentIds);
// ONLY allow registration from group context
// Design principle: One Group = One Project = One Team
// This enforces project isolation and prevents accidental cross-registration.
// You must be IN the group to register it, making the binding explicit and intentional.
if (context.type !== "group") {
return jsonResult({
success: false,
error: "Project registration can only be done from the Telegram/WhatsApp group you're registering.",
recommendation:
context.type === "via-agent"
? "If you're setting up DevClaw for the first time, use onboard. Then go to the project's Telegram/WhatsApp group to register it."
: "Please go to the Telegram/WhatsApp group you want to register and call project_register from there.",
contextGuidance: generateGuardrails(context),
});
}
// Auto-populate projectGroupId if not provided (use current group)
const actualGroupId = groupId || ctx.sessionKey;
if (!actualGroupId) {
throw new Error("Could not determine group ID from context. Please provide projectGroupId explicitly.");
}
// Provide helpful note if project is already registered
const contextInfo = context.projectName
? `Note: This group is already registered as "${context.projectName}". You may be re-registering it.`
: `Registering project for this ${context.channel} group (ID: ${actualGroupId.substring(0, 20)}...).`;
// 1. Check project not already registered (allow re-register if incomplete)
const data = await readProjects(workspaceDir);
const existing = data.projects[actualGroupId];
const existing = data.projects[groupId];
if (existing && existing.dev?.sessions && Object.keys(existing.dev.sessions).length > 0) {
throw new Error(
`Project already registered for this group: "${existing.name}". Remove the existing entry first or use a different group.`,
@@ -160,8 +129,8 @@ export function createProjectRegisterTool(api: OpenClawPluginApi) {
const healthy = await provider.healthCheck();
if (!healthy) {
const cliName = providerType === "github" ? "gh" : "glab";
const cliInstallUrl = providerType === "github"
? "https://cli.github.com"
const cliInstallUrl = providerType === "github"
? "https://cli.github.com"
: "https://gitlab.com/gitlab-org/cli";
throw new Error(
`${providerType.toUpperCase()} health check failed for ${repoPath}. ` +
@@ -176,14 +145,14 @@ export function createProjectRegisterTool(api: OpenClawPluginApi) {
await provider.ensureAllStateLabels();
// 5. Add project to projects.json
data.projects[actualGroupId] = {
data.projects[groupId] = {
name,
repo,
groupName,
deployUrl,
baseBranch,
deployBranch,
channel: context.channel,
channel,
roleExecution,
dev: emptyWorkerState([...DEV_LEVELS]),
qa: emptyWorkerState([...QA_LEVELS]),
@@ -197,7 +166,7 @@ export function createProjectRegisterTool(api: OpenClawPluginApi) {
// 7. Audit log
await auditLog(workspaceDir, "project_register", {
project: name,
groupId: actualGroupId,
groupId,
repo,
baseBranch,
deployBranch,
@@ -206,20 +175,18 @@ export function createProjectRegisterTool(api: OpenClawPluginApi) {
// 8. Return announcement
const promptsNote = promptsCreated ? " Prompt files scaffolded." : "";
const announcement = `📋 Project "${name}" registered for group ${groupName}. Labels created.${promptsNote} Ready for tasks.`;
const announcement = `Project "${name}" registered for group ${groupName}. Labels created.${promptsNote} Ready for tasks.`;
return jsonResult({
success: true,
project: name,
groupId: actualGroupId,
groupId,
repo,
baseBranch,
deployBranch,
labelsCreated: 8,
promptsScaffolded: promptsCreated,
announcement,
...(contextInfo && { contextInfo }),
contextGuidance: generateGuardrails(context),
});
},
});

View File

@@ -3,22 +3,20 @@
*
* Shows worker state and queue counts per project. No health checks
* (use `health` tool), no complex sequencing.
* Context-aware: auto-filters to project in group chats.
*/
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
import { jsonResult } from "openclaw/plugin-sdk";
import type { ToolContext } from "../types.js";
import { readProjects, getProject } from "../projects.js";
import { generateGuardrails } from "../context-guard.js";
import { log as auditLog } from "../audit.js";
import { fetchProjectQueues, type QueueLabel } from "../services/queue.js";
import { requireWorkspaceDir, resolveContext, getPluginConfig } from "../tool-helpers.js";
import { requireWorkspaceDir, getPluginConfig } from "../tool-helpers.js";
export function createStatusTool(api: OpenClawPluginApi) {
return (ctx: ToolContext) => ({
name: "status",
label: "Status",
description: `Show task queue and worker state per project. Context-aware: auto-filters in group chats. Use \`health\` tool for worker health checks.`,
description: `Show task queue and worker state per project. Use \`health\` tool for worker health checks.`,
parameters: {
type: "object",
properties: {
@@ -28,20 +26,7 @@ export function createStatusTool(api: OpenClawPluginApi) {
async execute(_id: string, params: Record<string, unknown>) {
const workspaceDir = requireWorkspaceDir(ctx);
const context = await resolveContext(ctx, api);
if (context.type === "via-agent") {
return jsonResult({
success: false,
warning: "status is for operational use, not setup.",
recommendation: "Use onboard instead for DevClaw setup.",
contextGuidance: generateGuardrails(context),
});
}
// Auto-filter in group context
let groupId = params.projectGroupId as string | undefined;
if (context.type === "group" && !groupId) groupId = context.groupId;
const groupId = params.projectGroupId as string | undefined;
const pluginConfig = getPluginConfig(api);
const projectExecution = (pluginConfig?.projectExecution as string) ?? "parallel";
@@ -80,11 +65,6 @@ export function createStatusTool(api: OpenClawPluginApi) {
success: true,
execution: { projectExecution },
projects: filtered,
context: {
type: context.type,
...(context.type === "group" && { projectName: context.projectName, autoFiltered: !params.projectGroupId }),
},
contextGuidance: generateGuardrails(context),
});
},
});

View File

@@ -15,7 +15,7 @@ import { dispatchTask } from "../dispatch.js";
import { notify, getNotificationConfig } from "../notify.js";
import { findNextIssue, detectRoleFromLabel, detectLevelFromLabels } from "../services/tick.js";
import { isDevLevel } from "../tiers.js";
import { requireWorkspaceDir, resolveContext, resolveProject, resolveProvider, groupOnlyError, getPluginConfig, tickAndNotify } from "../tool-helpers.js";
import { requireWorkspaceDir, resolveProject, resolveProvider, getPluginConfig } from "../tool-helpers.js";
export function createWorkStartTool(api: OpenClawPluginApi) {
return (ctx: ToolContext) => ({
@@ -24,10 +24,11 @@ export function createWorkStartTool(api: OpenClawPluginApi) {
description: `Pick up a task from the issue queue. ONLY works in project group chats. Handles label transition, level assignment, session creation, dispatch, and audit. Picks up only the explicitly requested issue.`,
parameters: {
type: "object",
required: ["projectGroupId"],
properties: {
projectGroupId: { type: "string", description: "Project group ID." },
issueId: { type: "number", description: "Issue ID. If omitted, picks next by priority." },
role: { type: "string", enum: ["dev", "qa"], description: "Worker role. Auto-detected from label if omitted." },
projectGroupId: { type: "string", description: "Project group ID. Auto-detected from group context." },
level: { type: "string", description: "Developer level (junior/medior/senior/reviewer). Auto-detected if omitted." },
},
},
@@ -35,15 +36,11 @@ export function createWorkStartTool(api: OpenClawPluginApi) {
async execute(_id: string, params: Record<string, unknown>) {
const issueIdParam = params.issueId as number | undefined;
const roleParam = params.role as "dev" | "qa" | undefined;
const groupIdParam = params.projectGroupId as string | undefined;
const groupId = params.projectGroupId as string;
const levelParam = (params.level ?? params.tier) as string | undefined;
const workspaceDir = requireWorkspaceDir(ctx);
// Context guard: group only
const context = await resolveContext(ctx, api);
if (context.type !== "group") return groupOnlyError("work_start", context);
const groupId = groupIdParam ?? context.groupId;
if (!groupId) throw new Error("projectGroupId is required");
const { project } = await resolveProject(workspaceDir, groupId);
const { provider } = resolveProvider(project);
@@ -107,7 +104,7 @@ export function createWorkStartTool(api: OpenClawPluginApi) {
const notifyConfig = getNotificationConfig(pluginConfig);
await notify(
{ type: "workerStart", project: project.name, groupId, issueId: issue.iid, issueTitle: issue.title, issueUrl: issue.web_url, role, level: dr.level, sessionAction: dr.sessionAction },
{ workspaceDir, config: notifyConfig, groupId, channel: context.channel },
{ workspaceDir, config: notifyConfig, groupId, channel: project.channel ?? "telegram" },
);
// Auto-tick disabled per issue #125 - work_start should only pick up the explicitly requested issue
@@ -118,7 +115,7 @@ export function createWorkStartTool(api: OpenClawPluginApi) {
role, level: dr.level, model: dr.model, sessionAction: dr.sessionAction,
announcement: dr.announcement, labelTransition: `${currentLabel}${targetLabel}`,
levelReason, levelSource,
autoDetected: { projectGroupId: !groupIdParam, role: !roleParam, issueId: issueIdParam === undefined, level: !levelParam },
autoDetected: { role: !roleParam, issueId: issueIdParam === undefined, level: !levelParam },
};
// tickPickups removed with auto-tick