@teamclaws/teamclaw 2026.3.26-2 → 2026.4.2-2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +52 -8
- package/cli.mjs +538 -224
- package/index.ts +76 -27
- package/openclaw.plugin.json +53 -28
- package/package.json +5 -2
- package/skills/teamclaw/SKILL.md +213 -0
- package/skills/teamclaw/references/api-quick-ref.md +117 -0
- package/skills/teamclaw-setup/SKILL.md +81 -0
- package/skills/teamclaw-setup/references/install-modes.md +136 -0
- package/skills/teamclaw-setup/references/validation-checklist.md +73 -0
- package/src/config.ts +44 -16
- package/src/controller/controller-capacity.ts +2 -2
- package/src/controller/controller-service.ts +193 -47
- package/src/controller/controller-tools.ts +102 -2
- package/src/controller/delivery-report.ts +563 -0
- package/src/controller/http-server.ts +1907 -172
- package/src/controller/kickoff-orchestrator.ts +292 -0
- package/src/controller/managed-gateway-process.ts +330 -0
- package/src/controller/orchestration-manifest.ts +69 -1
- package/src/controller/preview-manager.ts +676 -0
- package/src/controller/prompt-injector.ts +116 -67
- package/src/controller/role-inference.ts +41 -0
- package/src/controller/websocket.ts +3 -1
- package/src/controller/worker-provisioning.ts +429 -74
- package/src/discovery.ts +1 -1
- package/src/git-collaboration.ts +198 -47
- package/src/identity.ts +12 -2
- package/src/interaction-contracts.ts +179 -3
- package/src/networking.ts +99 -0
- package/src/openclaw-workspace.ts +478 -11
- package/src/prompt-policy.ts +381 -0
- package/src/roles.ts +37 -36
- package/src/state.ts +40 -1
- package/src/task-executor.ts +282 -78
- package/src/types.ts +150 -7
- package/src/ui/app.js +1403 -175
- package/src/ui/assets/teamclaw-app-icon.png +0 -0
- package/src/ui/index.html +122 -40
- package/src/ui/style.css +829 -143
- package/src/worker/http-handler.ts +40 -4
- package/src/worker/prompt-injector.ts +9 -38
- package/src/worker/skill-installer.ts +2 -2
- package/src/worker/tools.ts +31 -5
- package/src/worker/worker-service.ts +49 -8
- package/src/workspace-browser.ts +20 -7
- package/src/controller/local-worker-manager.ts +0 -533
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
import type { PluginConfig, TeamState } from "../types.js";
|
|
2
2
|
import { ROLES } from "../roles.js";
|
|
3
|
+
import {
|
|
4
|
+
buildControllerCompletionRules,
|
|
5
|
+
buildControllerDisciplineRules,
|
|
6
|
+
buildControllerEvidenceMemoryRules,
|
|
7
|
+
buildControllerIntakeRules,
|
|
8
|
+
buildControllerStructuredContractRules,
|
|
9
|
+
buildControllerToolRules,
|
|
10
|
+
buildControllerWorkflowRules,
|
|
11
|
+
composePrompt,
|
|
12
|
+
} from "../prompt-policy.js";
|
|
3
13
|
import { hasOnDemandWorkerProvisioning, shouldBlockControllerWithoutWorkers } from "./controller-capacity.js";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
"architect",
|
|
8
|
-
"developer",
|
|
9
|
-
"qa",
|
|
10
|
-
"release-engineer",
|
|
11
|
-
"infra-engineer",
|
|
12
|
-
"devops",
|
|
13
|
-
"security-engineer",
|
|
14
|
-
"designer",
|
|
15
|
-
"marketing",
|
|
16
|
-
].join(", ");
|
|
14
|
+
import { resolveTeamClawWorkspaceDir } from "../workspace-browser.js";
|
|
15
|
+
import fs from "node:fs";
|
|
16
|
+
import path from "node:path";
|
|
17
17
|
|
|
18
18
|
export type ControllerPromptDeps = {
|
|
19
19
|
config: PluginConfig;
|
|
@@ -30,18 +30,13 @@ export function createControllerPromptInjector(deps: ControllerPromptDeps) {
|
|
|
30
30
|
const blockedTasks = tasks.filter((t) => t.status === "blocked");
|
|
31
31
|
const completedTasks = tasks.filter((t) => t.status === "completed");
|
|
32
32
|
const pendingClarifications = Object.values(state?.clarifications ?? {}).filter((c) => c.status === "pending");
|
|
33
|
+
const canProvisionWithoutWorkers = hasOnDemandWorkerProvisioning(deps.config);
|
|
33
34
|
|
|
34
35
|
const parts: string[] = [
|
|
35
36
|
"## TeamClaw Controller Mode",
|
|
36
37
|
"You are the Team Controller and the first-pass requirements analyst for the human.",
|
|
37
38
|
"Treat human input as raw requirements unless it is already explicitly phrased as an execution-ready TeamClaw task.",
|
|
38
|
-
|
|
39
|
-
"### Available Tools",
|
|
40
|
-
"- teamclaw_create_task: Create a new task with role assignment",
|
|
41
|
-
"- teamclaw_submit_manifest: Submit the required structured orchestration manifest for this intake run",
|
|
42
|
-
"- teamclaw_list_tasks: List all tasks with status filtering",
|
|
43
|
-
"- teamclaw_assign_task: Assign a task to a specific worker",
|
|
44
|
-
"- teamclaw_send_message: Send messages between team members",
|
|
39
|
+
...buildControllerToolRules(),
|
|
45
40
|
"",
|
|
46
41
|
"### Current Team Status",
|
|
47
42
|
];
|
|
@@ -95,62 +90,116 @@ export function createControllerPromptInjector(deps: ControllerPromptDeps) {
|
|
|
95
90
|
parts.push(`- ${role.icon} ${role.label}: ${role.description}.${skillLine}`);
|
|
96
91
|
}
|
|
97
92
|
|
|
93
|
+
// List existing projects so the controller can distinguish new vs. existing
|
|
94
|
+
parts.push("");
|
|
95
|
+
parts.push("### Existing Projects in Workspace");
|
|
96
|
+
const existingProjects = listExistingProjects(state);
|
|
97
|
+
if (existingProjects.length === 0) {
|
|
98
|
+
parts.push("- No existing projects yet.");
|
|
99
|
+
} else {
|
|
100
|
+
for (const proj of existingProjects) {
|
|
101
|
+
parts.push(`- 📂 ${proj.dir}: ${proj.summary}`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
98
105
|
parts.push("");
|
|
99
|
-
parts.push("##
|
|
100
|
-
parts.push("-
|
|
101
|
-
parts.push("-
|
|
102
|
-
parts.push("-
|
|
103
|
-
parts.push("-
|
|
104
|
-
parts.push("-
|
|
106
|
+
parts.push("## New vs. Existing Project Detection");
|
|
107
|
+
parts.push("- Before creating tasks, determine if the user's request relates to an existing project listed above.");
|
|
108
|
+
parts.push("- If the request mentions a technology, feature, or project name that matches an existing project, treat it as an enhancement/bugfix for that project — reuse the same projectDir.");
|
|
109
|
+
parts.push("- If the request is clearly a new, unrelated requirement, create a fresh projectDir with a new projectName.");
|
|
110
|
+
parts.push("- When enhancing an existing project, include context about what already exists so the worker can extend rather than rebuild.");
|
|
111
|
+
parts.push("- NEVER let a worker's deliverables reference files from a different project. Each task's deliverables must be scoped to its own projectDir.");
|
|
112
|
+
|
|
113
|
+
parts.push(...buildControllerWorkflowRules());
|
|
105
114
|
|
|
106
115
|
parts.push("");
|
|
107
|
-
parts.push("##
|
|
108
|
-
parts.push("-
|
|
109
|
-
parts.push("-
|
|
110
|
-
parts.push("-
|
|
111
|
-
parts.push("- Use
|
|
112
|
-
parts.push("-
|
|
113
|
-
parts.push("-
|
|
114
|
-
parts.push("-
|
|
116
|
+
parts.push("## Team Kickoff Meeting (Collaborative Planning)");
|
|
117
|
+
parts.push("- **IMPORTANT**: When the requirement involves 3 or more roles, you MUST call teamclaw_request_kickoff as your FIRST tool call, BEFORE creating any tasks.");
|
|
118
|
+
parts.push("- The kickoff provisions candidate role workers and asks each for a structured assessment from their professional perspective.");
|
|
119
|
+
parts.push("- Each role evaluates: whether they're needed, their scope of work, suggested tasks, dependencies on other roles, risks, and open questions.");
|
|
120
|
+
parts.push("- Use the team's assessments to make informed decisions about which roles to actually involve and how to structure the task pipeline.");
|
|
121
|
+
parts.push("- Roles that assessed themselves as not needed will be automatically reclaimed after their idle timeout.");
|
|
122
|
+
parts.push("- Adaptive kickoff rules:");
|
|
123
|
+
parts.push(" - **Simple** (single clear task, 1 role): Skip kickoff, create task directly.");
|
|
124
|
+
parts.push(" - **Medium** (2-3 roles likely): Call kickoff with the 2-3 most relevant roles.");
|
|
125
|
+
parts.push(" - **Complex** (4+ potential roles, multi-domain, unclear scope): ALWAYS call kickoff with all candidate roles. Do NOT skip.");
|
|
126
|
+
parts.push("- For task_follow_up runs (triggered by completed tasks), do NOT call kickoff again — the team plan was already established.");
|
|
127
|
+
parts.push("- Example: 'Build an e-commerce platform' → complex (architect+developer+designer+qa+security) → MUST call teamclaw_request_kickoff first");
|
|
115
128
|
|
|
116
129
|
parts.push("");
|
|
117
|
-
parts.push("##
|
|
118
|
-
parts.push("-
|
|
119
|
-
parts.push("-
|
|
120
|
-
parts.push("- If
|
|
121
|
-
parts.push("-
|
|
122
|
-
parts.push("- When creating a task, include a recommendedSkills array whenever you know a useful OpenClaw/ClawHub skill slug (or a short search query if you do not know the exact slug).");
|
|
123
|
-
parts.push("- Prefer exact skill slugs over vague labels so the assigned worker can auto-search/install them before starting.");
|
|
124
|
-
parts.push("- 'Minimum task packet' means only tasks that can start immediately with the currently available information and already-satisfied prerequisites.");
|
|
125
|
-
parts.push("- If later phases depend on outputs that do not exist yet, describe them to the human as the plan, but do not create those TeamClaw tasks yet.");
|
|
126
|
-
parts.push("- Downstream QA/review/release/README/integration tasks must stay in the plan until the upstream code or artifacts already exist in the workspace.");
|
|
127
|
-
parts.push("- Do not dump raw user wording directly onto workers when the requirement still needs controller-side analysis.");
|
|
128
|
-
parts.push("- TeamClaw uses git as the default file collaboration mechanism. Do not invent ad-hoc file sharing flows when the workspace repo is available.");
|
|
130
|
+
parts.push("## Out-of-Scope Requests");
|
|
131
|
+
parts.push("- TeamClaw is a software development team. You handle: coding, architecture, design, testing, deployment, documentation, security review, and related technical work.");
|
|
132
|
+
parts.push("- If the human asks for something clearly non-technical (cooking, weather, personal advice, general knowledge, creative writing unrelated to software), politely decline in your reply text AND still call teamclaw_submit_manifest with createdTasks=[], requiredRoles=[], and requirementSummary explaining why you declined.");
|
|
133
|
+
parts.push("- If the request is borderline (e.g. 'write a blog post about our API'), lean toward accepting it and assigning to the appropriate role (marketing, pm).");
|
|
134
|
+
parts.push("- REMEMBER: You must ALWAYS call teamclaw_submit_manifest, even when declining. The system cannot record your decision without it.");
|
|
129
135
|
|
|
130
136
|
parts.push("");
|
|
131
|
-
parts.push("##
|
|
132
|
-
parts.push("-
|
|
133
|
-
parts.push("-
|
|
134
|
-
parts.push("-
|
|
135
|
-
parts.push("-
|
|
136
|
-
|
|
137
|
-
parts.push("
|
|
138
|
-
parts.push("
|
|
139
|
-
parts.push("-
|
|
140
|
-
parts.push("-
|
|
141
|
-
parts.push("-
|
|
142
|
-
parts.push("-
|
|
143
|
-
parts.push("-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
parts.push(
|
|
150
|
-
parts.push(
|
|
137
|
+
parts.push("## Clarification & Follow-up Awareness");
|
|
138
|
+
parts.push("- If this session has prior messages where you asked clarification questions, the human's new message is likely a response to those questions.");
|
|
139
|
+
parts.push("- Do NOT treat a follow-up human message as a brand-new requirement if there are pending clarification questions in this session. Instead, interpret the message as an answer and proceed with task creation.");
|
|
140
|
+
parts.push("- If the human's follow-up is clearly irrelevant to the pending questions (e.g. random chitchat), acknowledge it briefly and re-state the pending questions so the human knows what you still need.");
|
|
141
|
+
parts.push("- For automatic task_follow_up runs (triggered by task completion), focus on advancing the pipeline — do not re-ask questions already answered.");
|
|
142
|
+
|
|
143
|
+
parts.push("");
|
|
144
|
+
parts.push("## Deliverable Presentation");
|
|
145
|
+
parts.push("- When a task completes with a result contract, review the deliverables and present a clear, actionable summary to the human.");
|
|
146
|
+
parts.push("- For web applications: include the preview URL if available (deliverable.liveUrl). The human should be able to click and verify.");
|
|
147
|
+
parts.push("- For CLI tools: include the command to run with example arguments.");
|
|
148
|
+
parts.push("- For documents: highlight the key decisions or structure.");
|
|
149
|
+
parts.push("- When ALL tasks for the requirement are complete (requirementFullyComplete=true), provide a final delivery summary:");
|
|
150
|
+
parts.push(" - What was built (1-2 sentence overview)");
|
|
151
|
+
parts.push(" - How to access/run it (URLs, commands)");
|
|
152
|
+
parts.push(" - File locations (project directory)");
|
|
153
|
+
parts.push(" - Any caveats or next steps");
|
|
154
|
+
|
|
155
|
+
parts.push(...buildControllerStructuredContractRules());
|
|
156
|
+
parts.push(...buildControllerEvidenceMemoryRules());
|
|
157
|
+
parts.push(...buildControllerIntakeRules());
|
|
158
|
+
parts.push(...buildControllerDisciplineRules({ canProvisionWithoutWorkers }));
|
|
159
|
+
parts.push(...buildControllerCompletionRules());
|
|
151
160
|
|
|
152
161
|
return {
|
|
153
|
-
prependSystemContext: parts
|
|
162
|
+
prependSystemContext: composePrompt(parts),
|
|
154
163
|
};
|
|
155
164
|
};
|
|
156
165
|
}
|
|
166
|
+
|
|
167
|
+
type ExistingProjectInfo = { dir: string; summary: string };
|
|
168
|
+
|
|
169
|
+
function listExistingProjects(state: TeamState | null): ExistingProjectInfo[] {
|
|
170
|
+
const projects: ExistingProjectInfo[] = [];
|
|
171
|
+
|
|
172
|
+
// Gather from completed tasks with projectDir
|
|
173
|
+
const seenDirs = new Set<string>();
|
|
174
|
+
if (state) {
|
|
175
|
+
for (const task of Object.values(state.tasks)) {
|
|
176
|
+
if (task.projectDir && !seenDirs.has(task.projectDir)) {
|
|
177
|
+
seenDirs.add(task.projectDir);
|
|
178
|
+
const summary = task.resultContract?.summary ?? task.title;
|
|
179
|
+
projects.push({ dir: task.projectDir, summary });
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Also scan the filesystem for project directories not tracked in state
|
|
185
|
+
try {
|
|
186
|
+
const workspaceDir = resolveTeamClawWorkspaceDir();
|
|
187
|
+
const projectsRoot = path.join(workspaceDir, "projects");
|
|
188
|
+
if (fs.existsSync(projectsRoot)) {
|
|
189
|
+
const entries = fs.readdirSync(projectsRoot, { withFileTypes: true });
|
|
190
|
+
for (const entry of entries) {
|
|
191
|
+
if (entry.isDirectory() && !entry.name.startsWith(".")) {
|
|
192
|
+
const fullDir = `projects/${entry.name}`;
|
|
193
|
+
if (!seenDirs.has(entry.name) && !seenDirs.has(fullDir)) {
|
|
194
|
+
seenDirs.add(fullDir);
|
|
195
|
+
projects.push({ dir: fullDir, summary: "(discovered on filesystem)" });
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
} catch {
|
|
201
|
+
// Workspace not available — skip filesystem scan
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return projects.slice(0, 20);
|
|
205
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { ROLES } from "../roles.js";
|
|
2
|
+
import type { RoleId, TaskInfo } from "../types.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Infer the most appropriate role for a task based on text analysis.
|
|
6
|
+
*
|
|
7
|
+
* If `task.assignedRole` is already set, returns it directly.
|
|
8
|
+
* Otherwise, tokenizes the task title + description and scores each known
|
|
9
|
+
* role by overlap with its id, label, and capabilities.
|
|
10
|
+
*/
|
|
11
|
+
export function inferTaskRole(task: Pick<TaskInfo, "assignedRole" | "title" | "description">): RoleId | null {
|
|
12
|
+
if (task.assignedRole) {
|
|
13
|
+
return task.assignedRole;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const text = `${task.title} ${task.description}`.toLowerCase();
|
|
17
|
+
let bestRole: RoleId | null = null;
|
|
18
|
+
let bestScore = 0;
|
|
19
|
+
|
|
20
|
+
for (const role of ROLES) {
|
|
21
|
+
const roleTokens = [
|
|
22
|
+
role.id,
|
|
23
|
+
role.label,
|
|
24
|
+
...role.capabilities,
|
|
25
|
+
].flatMap((entry) => entry.toLowerCase().split(/[^a-z0-9]+/).filter((token) => token.length > 2));
|
|
26
|
+
const uniqueTokens = [...new Set(roleTokens)];
|
|
27
|
+
const score = uniqueTokens.reduce((count, token) => count + (text.includes(token) ? 1 : 0), 0);
|
|
28
|
+
if (score > bestScore) {
|
|
29
|
+
bestScore = score;
|
|
30
|
+
bestRole = role.id;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return bestScore > 0 ? bestRole : null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Fallback role when inference produces no match.
|
|
39
|
+
* The developer role is the most versatile generalist.
|
|
40
|
+
*/
|
|
41
|
+
export const FALLBACK_ROLE: RoleId = "developer";
|
|
@@ -12,7 +12,9 @@ export type WsEvent =
|
|
|
12
12
|
| { type: "task:execution"; data: unknown }
|
|
13
13
|
| { type: "message:new"; data: unknown }
|
|
14
14
|
| { type: "clarification:requested"; data: unknown }
|
|
15
|
-
| { type: "clarification:answered"; data: unknown }
|
|
15
|
+
| { type: "clarification:answered"; data: unknown }
|
|
16
|
+
| { type: "requirement:complete"; data: unknown }
|
|
17
|
+
| { type: "report:ready"; data: unknown };
|
|
16
18
|
|
|
17
19
|
export class TeamWebSocketServer {
|
|
18
20
|
private wss: WebSocketServer | null = null;
|