@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.
Files changed (46) hide show
  1. package/README.md +52 -8
  2. package/cli.mjs +538 -224
  3. package/index.ts +76 -27
  4. package/openclaw.plugin.json +53 -28
  5. package/package.json +5 -2
  6. package/skills/teamclaw/SKILL.md +213 -0
  7. package/skills/teamclaw/references/api-quick-ref.md +117 -0
  8. package/skills/teamclaw-setup/SKILL.md +81 -0
  9. package/skills/teamclaw-setup/references/install-modes.md +136 -0
  10. package/skills/teamclaw-setup/references/validation-checklist.md +73 -0
  11. package/src/config.ts +44 -16
  12. package/src/controller/controller-capacity.ts +2 -2
  13. package/src/controller/controller-service.ts +193 -47
  14. package/src/controller/controller-tools.ts +102 -2
  15. package/src/controller/delivery-report.ts +563 -0
  16. package/src/controller/http-server.ts +1907 -172
  17. package/src/controller/kickoff-orchestrator.ts +292 -0
  18. package/src/controller/managed-gateway-process.ts +330 -0
  19. package/src/controller/orchestration-manifest.ts +69 -1
  20. package/src/controller/preview-manager.ts +676 -0
  21. package/src/controller/prompt-injector.ts +116 -67
  22. package/src/controller/role-inference.ts +41 -0
  23. package/src/controller/websocket.ts +3 -1
  24. package/src/controller/worker-provisioning.ts +429 -74
  25. package/src/discovery.ts +1 -1
  26. package/src/git-collaboration.ts +198 -47
  27. package/src/identity.ts +12 -2
  28. package/src/interaction-contracts.ts +179 -3
  29. package/src/networking.ts +99 -0
  30. package/src/openclaw-workspace.ts +478 -11
  31. package/src/prompt-policy.ts +381 -0
  32. package/src/roles.ts +37 -36
  33. package/src/state.ts +40 -1
  34. package/src/task-executor.ts +282 -78
  35. package/src/types.ts +150 -7
  36. package/src/ui/app.js +1403 -175
  37. package/src/ui/assets/teamclaw-app-icon.png +0 -0
  38. package/src/ui/index.html +122 -40
  39. package/src/ui/style.css +829 -143
  40. package/src/worker/http-handler.ts +40 -4
  41. package/src/worker/prompt-injector.ts +9 -38
  42. package/src/worker/skill-installer.ts +2 -2
  43. package/src/worker/tools.ts +31 -5
  44. package/src/worker/worker-service.ts +49 -8
  45. package/src/workspace-browser.ts +20 -7
  46. 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
- const TEAMCLAW_ROLE_IDS_TEXT = [
6
- "pm",
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("## Controller Workflow");
100
- parts.push("- First determine which TeamClaw roles are needed for the human requirement.");
101
- parts.push("- Then translate the requirement into the minimum execution-ready TeamClaw tasks owned by those roles.");
102
- parts.push("- TeamClaw workers, not the controller, do the specialist work in the shared repo/workspace.");
103
- parts.push("- After workers report progress, results, or handoffs, create only the next tasks whose prerequisites are now satisfied.");
104
- parts.push("- A completed upstream task with a structured result contract, concrete deliverables, or an explicit handoff is strong evidence that its dependent downstream work can now be created.");
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("## Structured Orchestration Contract");
108
- parts.push("- Freeform prose is not enough for TeamClaw scheduling decisions.");
109
- parts.push("- After your analysis and task-creation decisions are complete, call teamclaw_submit_manifest exactly once for this intake run.");
110
- parts.push("- The manifest must include: requirementSummary, requiredRoles, clarificationsNeeded, clarificationQuestions, createdTasks, deferredTasks, and any handoff notes.");
111
- parts.push("- Use createdTasks for execution-ready tasks that this run activated now, including a deliberately reused existing TeamClaw task when you chose not to duplicate it.");
112
- parts.push("- Use deferredTasks for later-phase work that should not be created yet because prerequisites are not satisfied.");
113
- parts.push("- If the run is blocked and no tasks should be created yet, submit a manifest with createdTasks=[] and explain the blocker in clarificationQuestions and/or deferredTasks.");
114
- parts.push("- If you ask the human clarifying questions, still submit the manifest so the controller has machine-readable state for this run.");
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("## Requirement Intake Rules");
118
- parts.push("- Human messages are the initial requirement, not an already-decomposed task tree.");
119
- parts.push("- First analyze the requirement: desired outcome, scope, constraints, acceptance signals, and missing decisions.");
120
- parts.push("- If critical information is missing, ask the human a concrete clarification question before creating execution tasks.");
121
- parts.push("- After the requirement is clear enough, translate it into the minimum explicit TeamClaw task packet needed for the team.");
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("## Controller Discipline");
132
- parts.push("- Stay within the user's current requirement/request.");
133
- parts.push("- Create tasks only after you have converted the raw requirement into an execution-ready packet.");
134
- parts.push("- Never create backlog placeholder tasks or future-phase tasks with unmet prerequisites; TeamClaw tasks are live work items, not a passive roadmap.");
135
- parts.push("- Never create a task whose own wording says it should happen after something else is completed, ready, validated, or merged.");
136
- parts.push("- Bad example: creating a QA/integration task that says 'run after server and SDK are ready' before those outputs exist. Good example: mention that QA step in the plan now, then create it later when the repo already contains the server and SDK.");
137
- parts.push("- Do not auto-spawn helper tasks, duplicate tasks, or parallel task trees.");
138
- parts.push("- Do not let a worker task turn itself into a controller/coordinator workflow.");
139
- parts.push("- If the correct role is busy, prefer waiting, messaging, or explicit reassignment over routing core work to an unrelated role.");
140
- parts.push("- If a task is blocked by missing information, keep it in the clarification queue until the human answers; do not guess on the user's behalf.");
141
- parts.push("- You are never a substitute worker. Do not personally perform architecture, implementation, QA, release, infra, design, marketing, research, or other specialist work.");
142
- parts.push("- Your own reply must stay at the orchestration layer: clarification, role selection, task decomposition, assignment decisions, and concise status updates.");
143
- parts.push("- Do not rely on unstructured reply text as the only description of your orchestration decisions; the manifest is mandatory.");
144
- if (hasOnDemandWorkerProvisioning(deps.config)) {
145
- parts.push("- If no workers are currently registered but on-demand provisioning is enabled, you may still create execution-ready tasks so the required roles can be provisioned.");
146
- } else {
147
- parts.push("- If no workers are registered, you may mention which roles would be needed, but stop there and report the worker-capacity block to the human.");
148
- }
149
- parts.push("- Use the controller itself for requirement analysis; use the PM role only for PM-owned deliverables after intake is clear.");
150
- parts.push(`- Use exact TeamClaw role IDs only: ${TEAMCLAW_ROLE_IDS_TEXT}.`);
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.join("\n"),
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;