feat: Implement context detection and onboarding tools for DevClaw
- Add context-guard.ts to detect interaction context (via-agent, direct, group) and generate guardrails. - Introduce onboarding.ts for conversational onboarding context templates and workspace file checks. - Enhance setup.ts to support new agent creation with channel binding and migration of existing bindings. - Create analyze-channel-bindings.ts to analyze channel availability and detect binding conflicts. - Implement context-test.ts for debugging context detection. - Develop devclaw_onboard.ts for explicit onboarding tool that guides users through setup. - Update devclaw_setup.ts to include channel binding and migration support in setup process. - Modify project-register.ts to enforce project registration from group context and auto-populate group ID. - Enhance queue-status.ts to provide context-aware status checks and recommendations. - Update task tools (task-complete, task-create, task-pickup) to clarify group ID usage for Telegram/WhatsApp.
This commit is contained in:
@@ -17,6 +17,7 @@ import { createProvider } from "../providers/index.js";
|
||||
import { log as auditLog } from "../audit.js";
|
||||
import { DEV_TIERS, QA_TIERS } from "../tiers.js";
|
||||
import { DEFAULT_DEV_INSTRUCTIONS, DEFAULT_QA_INSTRUCTIONS } from "../templates.js";
|
||||
import { detectContext, generateGuardrails } from "../context-guard.js";
|
||||
|
||||
/**
|
||||
* Ensure default role files exist, then copy them into the project's role directory.
|
||||
@@ -72,14 +73,14 @@ export function createProjectRegisterTool(api: OpenClawPluginApi) {
|
||||
return (ctx: ToolContext) => ({
|
||||
name: "project_register",
|
||||
label: "Project Register",
|
||||
description: `Register a new project with DevClaw. Creates all required state labels (idempotent) and adds the project to projects.json. One-time setup per project. Auto-detects GitHub/GitLab from git remote.`,
|
||||
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.`,
|
||||
parameters: {
|
||||
type: "object",
|
||||
required: ["projectGroupId", "name", "repo", "groupName", "baseBranch"],
|
||||
required: ["name", "repo", "baseBranch"],
|
||||
properties: {
|
||||
projectGroupId: {
|
||||
type: "string",
|
||||
description: "Telegram group ID (will be the key in projects.json)",
|
||||
description: "Telegram/WhatsApp group ID (optional - auto-detected from current group if omitted)",
|
||||
},
|
||||
name: {
|
||||
type: "string",
|
||||
@@ -91,7 +92,7 @@ export function createProjectRegisterTool(api: OpenClawPluginApi) {
|
||||
},
|
||||
groupName: {
|
||||
type: "string",
|
||||
description: "Telegram group display name (e.g. 'Dev - My Project')",
|
||||
description: "Group display name (optional - defaults to 'Project: {name}')",
|
||||
},
|
||||
baseBranch: {
|
||||
type: "string",
|
||||
@@ -112,7 +113,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 groupName = params.groupName as string;
|
||||
const groupName = (params.groupName as string) ?? `Project: ${name}`;
|
||||
const baseBranch = params.baseBranch as string;
|
||||
const deployBranch = (params.deployBranch as string) ?? baseBranch;
|
||||
const deployUrl = (params.deployUrl as string) ?? "";
|
||||
@@ -122,12 +123,46 @@ 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 devclaw_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 === "whatsapp" ? "WhatsApp" : "Telegram"} 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[groupId];
|
||||
const existing = data.projects[actualGroupId];
|
||||
if (existing && existing.dev?.sessions && Object.keys(existing.dev.sessions).length > 0) {
|
||||
throw new Error(
|
||||
`Project already registered for groupId ${groupId}: "${existing.name}". Use a different group ID or remove the existing entry first.`,
|
||||
`Project already registered for this group: "${existing.name}". Remove the existing entry first or use a different group.`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -158,7 +193,7 @@ export function createProjectRegisterTool(api: OpenClawPluginApi) {
|
||||
await provider.ensureAllStateLabels();
|
||||
|
||||
// 5. Add project to projects.json
|
||||
data.projects[groupId] = {
|
||||
data.projects[actualGroupId] = {
|
||||
name,
|
||||
repo,
|
||||
groupName,
|
||||
@@ -178,7 +213,7 @@ export function createProjectRegisterTool(api: OpenClawPluginApi) {
|
||||
// 7. Audit log
|
||||
await auditLog(workspaceDir, "project_register", {
|
||||
project: name,
|
||||
groupId,
|
||||
groupId: actualGroupId,
|
||||
repo,
|
||||
baseBranch,
|
||||
deployBranch,
|
||||
@@ -192,13 +227,15 @@ export function createProjectRegisterTool(api: OpenClawPluginApi) {
|
||||
return jsonResult({
|
||||
success: true,
|
||||
project: name,
|
||||
groupId,
|
||||
groupId: actualGroupId,
|
||||
repo,
|
||||
baseBranch,
|
||||
deployBranch,
|
||||
labelsCreated: 8,
|
||||
rolesScaffolded: rolesCreated,
|
||||
announcement,
|
||||
...(contextInfo && { contextInfo }),
|
||||
contextGuidance: generateGuardrails(context),
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user