@teamclaws/teamclaw 2026.3.21

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.
@@ -0,0 +1,149 @@
1
+ import os from "node:os";
2
+ import type { PluginLogger } from "../api.js";
3
+ import type { PluginConfig, WorkerIdentity } from "./types.js";
4
+ import { generateId, createRegistrationRequest } from "./protocol.js";
5
+ import { getRole } from "./roles.js";
6
+ import { loadWorkerIdentity, saveWorkerIdentity, clearWorkerIdentity } from "./state.js";
7
+ import { MDnsBrowser } from "./discovery.js";
8
+
9
+ function getLocalIp(targetHost?: string): string {
10
+ if (!targetHost || targetHost === "localhost" || targetHost === "127.0.0.1") {
11
+ return "localhost";
12
+ }
13
+
14
+ const interfaces = os.networkInterfaces();
15
+ for (const name of Object.keys(interfaces)) {
16
+ const addrs = interfaces[name];
17
+ if (!addrs) continue;
18
+ for (const addr of addrs) {
19
+ if (addr.family === "IPv4" && !addr.internal) {
20
+ return addr.address;
21
+ }
22
+ }
23
+ }
24
+
25
+ return "localhost";
26
+ }
27
+
28
+ export class IdentityManager {
29
+ private config: PluginConfig;
30
+ private logger: PluginLogger;
31
+ private identity: WorkerIdentity | null = null;
32
+ private browser: MDnsBrowser;
33
+
34
+ constructor(config: PluginConfig, logger: PluginLogger) {
35
+ this.config = config;
36
+ this.logger = logger;
37
+ this.browser = new MDnsBrowser(logger);
38
+ }
39
+
40
+ hasIdentity(): boolean {
41
+ return this.identity !== null;
42
+ }
43
+
44
+ getIdentity(): WorkerIdentity | null {
45
+ return this.identity;
46
+ }
47
+
48
+ async discoverControllerUrl(): Promise<string | null> {
49
+ if (this.config.controllerUrl) {
50
+ return this.config.controllerUrl;
51
+ }
52
+
53
+ const results = await this.browser.browse(this.config.teamName, 5000);
54
+ if (results.length > 0) {
55
+ const controller = results[0]!;
56
+ return `http://${controller.host}:${controller.port}`;
57
+ }
58
+
59
+ return null;
60
+ }
61
+
62
+ async register(): Promise<WorkerIdentity | null> {
63
+ const requestedWorkerId = process.env.TEAMCLAW_WORKER_ID?.trim() || undefined;
64
+ const launchToken = process.env.TEAMCLAW_LAUNCH_TOKEN?.trim() || undefined;
65
+
66
+ const existing = await loadWorkerIdentity();
67
+ if (existing) {
68
+ if (requestedWorkerId && existing.workerId !== requestedWorkerId) {
69
+ await clearWorkerIdentity();
70
+ } else {
71
+ this.identity = existing;
72
+ this.logger.info(`Identity: restored existing identity (workerId=${existing.workerId})`);
73
+ return existing;
74
+ }
75
+ }
76
+
77
+ const restored = await loadWorkerIdentity();
78
+ if (restored) {
79
+ this.identity = restored;
80
+ this.logger.info(`Identity: restored existing identity (workerId=${restored.workerId})`);
81
+ return restored;
82
+ }
83
+
84
+ const controllerUrl = await this.discoverControllerUrl();
85
+ if (!controllerUrl) {
86
+ this.logger.warn("Identity: no controller found via mDNS or manual URL");
87
+ return null;
88
+ }
89
+
90
+ const roleDef = getRole(this.config.role);
91
+ const workerId = requestedWorkerId ?? generateId();
92
+ const localIp = getLocalIp(new URL(controllerUrl).hostname);
93
+ const workerUrl = `http://${localIp}:${this.config.port}`;
94
+
95
+ const registration = createRegistrationRequest(
96
+ workerId,
97
+ this.config.role,
98
+ roleDef?.label ?? this.config.role,
99
+ workerUrl,
100
+ roleDef?.capabilities ?? [],
101
+ launchToken,
102
+ );
103
+
104
+ try {
105
+ const res = await fetch(`${controllerUrl}/api/v1/workers/register`, {
106
+ method: "POST",
107
+ headers: { "Content-Type": "application/json" },
108
+ body: JSON.stringify(registration),
109
+ });
110
+
111
+ if (!res.ok) {
112
+ const text = await res.text();
113
+ this.logger.error(`Identity: registration failed (${res.status}): ${text}`);
114
+ return null;
115
+ }
116
+
117
+ const identity: WorkerIdentity = {
118
+ workerId,
119
+ role: this.config.role,
120
+ controllerUrl,
121
+ registeredAt: Date.now(),
122
+ };
123
+
124
+ this.identity = identity;
125
+ await saveWorkerIdentity(identity);
126
+ this.logger.info(`Identity: registered as ${this.config.role} (workerId=${workerId}) at ${controllerUrl}`);
127
+ return identity;
128
+ } catch (err) {
129
+ this.logger.error(`Identity: registration error: ${err instanceof Error ? err.message : String(err)}`);
130
+ return null;
131
+ }
132
+ }
133
+
134
+ async clear(): Promise<void> {
135
+ if (this.identity) {
136
+ try {
137
+ await fetch(`${this.identity.controllerUrl}/api/v1/workers/${this.identity.workerId}`, {
138
+ method: "DELETE",
139
+ });
140
+ } catch {
141
+ // ignore
142
+ }
143
+ }
144
+
145
+ this.identity = null;
146
+ await clearWorkerIdentity();
147
+ this.logger.info("Identity: cleared");
148
+ }
149
+ }
@@ -0,0 +1,101 @@
1
+ import os from "node:os";
2
+ import path from "node:path";
3
+ import fs from "node:fs/promises";
4
+ import type { PluginLogger } from "../api.js";
5
+
6
+ const DEFAULT_AGENTS_MD = `# AGENTS.md
7
+
8
+ This workspace is shared by TeamClaw controller and workers.
9
+
10
+ Rules:
11
+ - Treat task-provided file paths as hints; verify they exist before reading or editing.
12
+ - Use the shared \`memory/\` directory for lightweight notes when useful.
13
+ - Report meaningful progress during longer tasks.
14
+ - If requirements or environment details are missing and work cannot continue safely, request clarification instead of guessing.
15
+ `;
16
+
17
+ const DEFAULT_BOOTSTRAP_MD = `# BOOTSTRAP.md
18
+
19
+ This is a TeamClaw workspace bootstrap file.
20
+
21
+ If the project files you expect are missing:
22
+ 1. Search the workspace before assuming the path is correct.
23
+ 2. Call out missing artifacts explicitly.
24
+ 3. Ask for clarification when the missing artifact blocks the task.
25
+ `;
26
+
27
+ const DEFAULT_HEARTBEAT_MD = `# HEARTBEAT.md
28
+
29
+ # Keep this file empty (or with only comments) to skip heartbeat API calls.
30
+ `;
31
+
32
+ export function resolveDefaultOpenClawHomeDir(
33
+ env: NodeJS.ProcessEnv = process.env,
34
+ homedir: () => string = os.homedir,
35
+ ): string {
36
+ const baseHome = env.OPENCLAW_HOME?.trim() || env.HOME?.trim() || homedir();
37
+ return path.resolve(baseHome);
38
+ }
39
+
40
+ export function resolveDefaultOpenClawStateDir(
41
+ env: NodeJS.ProcessEnv = process.env,
42
+ homedir: () => string = os.homedir,
43
+ ): string {
44
+ const stateDirOverride = env.OPENCLAW_STATE_DIR?.trim();
45
+ if (stateDirOverride) {
46
+ return path.resolve(stateDirOverride);
47
+ }
48
+
49
+ return path.join(resolveDefaultOpenClawHomeDir(env, homedir), ".openclaw");
50
+ }
51
+
52
+ export function resolveDefaultOpenClawConfigPath(
53
+ env: NodeJS.ProcessEnv = process.env,
54
+ homedir: () => string = os.homedir,
55
+ ): string {
56
+ const configPathOverride = env.OPENCLAW_CONFIG_PATH?.trim();
57
+ if (configPathOverride) {
58
+ return path.resolve(configPathOverride);
59
+ }
60
+
61
+ return path.join(resolveDefaultOpenClawStateDir(env, homedir), "openclaw.json");
62
+ }
63
+
64
+ export function resolveDefaultOpenClawWorkspaceDir(
65
+ env: NodeJS.ProcessEnv = process.env,
66
+ homedir: () => string = os.homedir,
67
+ ): string {
68
+ const stateDir = resolveDefaultOpenClawStateDir(env, homedir);
69
+ const profile = env.OPENCLAW_PROFILE?.trim();
70
+ if (profile && profile.toLowerCase() !== "default") {
71
+ return path.join(stateDir, `workspace-${profile}`);
72
+ }
73
+ return path.join(stateDir, "workspace");
74
+ }
75
+
76
+ export async function ensureOpenClawWorkspaceMemoryDir(logger: PluginLogger): Promise<string> {
77
+ const workspaceDir = resolveDefaultOpenClawWorkspaceDir();
78
+ const memoryDir = path.join(workspaceDir, "memory");
79
+ try {
80
+ await fs.mkdir(workspaceDir, { recursive: true });
81
+ await fs.mkdir(memoryDir, { recursive: true });
82
+ await ensureFileIfMissing(path.join(workspaceDir, "AGENTS.md"), DEFAULT_AGENTS_MD);
83
+ await ensureFileIfMissing(path.join(workspaceDir, "BOOTSTRAP.md"), DEFAULT_BOOTSTRAP_MD);
84
+ await ensureFileIfMissing(path.join(workspaceDir, "HEARTBEAT.md"), DEFAULT_HEARTBEAT_MD);
85
+ } catch (err) {
86
+ logger.warn(
87
+ `TeamClaw: failed to ensure OpenClaw workspace memory dir at ${memoryDir}: ${
88
+ err instanceof Error ? err.message : String(err)
89
+ }`,
90
+ );
91
+ }
92
+ return memoryDir;
93
+ }
94
+
95
+ async function ensureFileIfMissing(filePath: string, content: string): Promise<void> {
96
+ try {
97
+ await fs.access(filePath);
98
+ } catch {
99
+ await fs.writeFile(filePath, content, "utf8");
100
+ }
101
+ }
@@ -0,0 +1,88 @@
1
+ import type { IncomingMessage, ServerResponse } from "node:http";
2
+ import type { HeartbeatPayload, RegistrationRequest, RoleId } from "./types.js";
3
+
4
+ export const MDNS_TYPE = "_teamclaw._tcp";
5
+ export const DEFAULT_PORT = 9527;
6
+ export const HEARTBEAT_MS = 10000;
7
+ export const WORKER_TIMEOUT_MS = 30000;
8
+ export const API_PREFIX = "/api/v1";
9
+
10
+ function generateId(): string {
11
+ const ts = Date.now().toString(36);
12
+ const rand = Math.random().toString(36).slice(2, 8);
13
+ return `${ts}-${rand}`;
14
+ }
15
+
16
+ async function parseJsonBody(req: IncomingMessage): Promise<Record<string, unknown>> {
17
+ const raw = await readRequestBody(req);
18
+ if (!raw.length) {
19
+ return {};
20
+ }
21
+
22
+ try {
23
+ return JSON.parse(raw.toString("utf8")) as Record<string, unknown>;
24
+ } catch (err) {
25
+ throw new Error(`Invalid JSON body: ${err instanceof Error ? err.message : String(err)}`);
26
+ }
27
+ }
28
+
29
+ async function readRequestBody(req: IncomingMessage): Promise<Buffer> {
30
+ return new Promise((resolve, reject) => {
31
+ const chunks: Buffer[] = [];
32
+ req.on("data", (chunk: Buffer) => chunks.push(chunk));
33
+ req.on("end", () => {
34
+ resolve(Buffer.concat(chunks));
35
+ });
36
+ req.on("error", reject);
37
+ });
38
+ }
39
+
40
+ function sendJson(res: ServerResponse, status: number, data: unknown): void {
41
+ const body = JSON.stringify(data);
42
+ res.writeHead(status, {
43
+ "Content-Type": "application/json",
44
+ "Content-Length": Buffer.byteLength(body),
45
+ "Access-Control-Allow-Origin": "*",
46
+ "Access-Control-Allow-Methods": "GET, POST, PATCH, DELETE, OPTIONS",
47
+ "Access-Control-Allow-Headers": "Content-Type",
48
+ });
49
+ res.end(body);
50
+ }
51
+
52
+ function sendError(res: ServerResponse, status: number, message: string): void {
53
+ sendJson(res, status, { error: message });
54
+ }
55
+
56
+ function createRegistrationRequest(
57
+ workerId: string,
58
+ role: RoleId,
59
+ label: string,
60
+ url: string,
61
+ capabilities: string[],
62
+ launchToken?: string,
63
+ ): RegistrationRequest {
64
+ return { workerId, role, label, url, capabilities, launchToken };
65
+ }
66
+
67
+ function createHeartbeatPayload(
68
+ workerId: string,
69
+ status: HeartbeatPayload["status"],
70
+ currentTaskId?: string,
71
+ ): HeartbeatPayload {
72
+ return {
73
+ workerId,
74
+ status,
75
+ currentTaskId,
76
+ timestamp: Date.now(),
77
+ };
78
+ }
79
+
80
+ export {
81
+ generateId,
82
+ parseJsonBody,
83
+ readRequestBody,
84
+ sendJson,
85
+ sendError,
86
+ createRegistrationRequest,
87
+ createHeartbeatPayload,
88
+ };
package/src/roles.ts ADDED
@@ -0,0 +1,275 @@
1
+ import type { RoleDefinition, RoleId } from "./types.js";
2
+
3
+ const ROLES: RoleDefinition[] = [
4
+ {
5
+ id: "pm",
6
+ label: "Product Manager",
7
+ icon: "\uD83D\uDCCB",
8
+ description: "Product planning, requirements analysis, user stories",
9
+ capabilities: [
10
+ "requirements-analysis", "user-stories", "product-specification",
11
+ "priority-planning", "stakeholder-communication",
12
+ ],
13
+ systemPrompt: [
14
+ "You are a Product Manager in a virtual software team.",
15
+ "Your responsibilities include analyzing requirements, writing user stories,",
16
+ "defining product specifications, and prioritizing features.",
17
+ "When receiving tasks, turn them into clear requirements and acceptance criteria inside your deliverable.",
18
+ "Always consider user impact and business value.",
19
+ ].join("\n"),
20
+ suggestedNextRoles: ["architect", "designer"],
21
+ },
22
+ {
23
+ id: "architect",
24
+ label: "Software Architect",
25
+ icon: "\uD83D\uDEE0\uFE0F",
26
+ description: "System design, technical architecture, API design",
27
+ capabilities: [
28
+ "system-design", "api-design", "database-schema",
29
+ "technology-selection", "code-review-architecture",
30
+ ],
31
+ systemPrompt: [
32
+ "You are a Software Architect in a virtual software team.",
33
+ "Your responsibilities include system design, API design, database schema design,",
34
+ "and technology selection.",
35
+ "When receiving tasks, provide detailed technical designs with clear component boundaries.",
36
+ "Consider scalability, maintainability, and performance.",
37
+ ].join("\n"),
38
+ suggestedNextRoles: ["developer", "devops"],
39
+ },
40
+ {
41
+ id: "developer",
42
+ label: "Developer",
43
+ icon: "\uD83D\uDCBB",
44
+ description: "Code implementation, bug fixes, feature development",
45
+ capabilities: [
46
+ "coding", "debugging", "feature-implementation",
47
+ "code-refactoring", "unit-testing",
48
+ ],
49
+ systemPrompt: [
50
+ "You are a Developer in a virtual software team.",
51
+ "Your responsibilities include implementing features, fixing bugs, refactoring code,",
52
+ "and writing unit tests.",
53
+ "Follow the architecture and design specifications provided by the architect.",
54
+ "Write clean, maintainable code with proper error handling.",
55
+ ].join("\n"),
56
+ suggestedNextRoles: ["qa", "developer"],
57
+ },
58
+ {
59
+ id: "qa",
60
+ label: "QA Engineer",
61
+ icon: "\uD83D\uDD0D",
62
+ description: "Testing, quality assurance, bug reporting",
63
+ capabilities: [
64
+ "test-planning", "test-case-writing", "bug-reporting",
65
+ "regression-testing", "quality-assurance",
66
+ ],
67
+ systemPrompt: [
68
+ "You are a QA Engineer in a virtual software team.",
69
+ "Your responsibilities include test planning, writing test cases, reporting bugs,",
70
+ "and ensuring quality standards.",
71
+ "When reviewing work, check for edge cases, error handling, and adherence to specifications.",
72
+ "Provide detailed, reproducible bug reports.",
73
+ ].join("\n"),
74
+ suggestedNextRoles: ["developer", "release-engineer"],
75
+ },
76
+ {
77
+ id: "release-engineer",
78
+ label: "Release Engineer",
79
+ icon: "\uD83D\uDE82",
80
+ description: "Release management, deployment, version control",
81
+ capabilities: [
82
+ "release-management", "deployment", "version-control",
83
+ "ci-cd-pipeline", "release-notes",
84
+ ],
85
+ systemPrompt: [
86
+ "You are a Release Engineer in a virtual software team.",
87
+ "Your responsibilities include managing releases, deployment pipelines,",
88
+ "version control, and writing release notes.",
89
+ "Ensure smooth and reliable deployments with proper rollback strategies.",
90
+ ].join("\n"),
91
+ suggestedNextRoles: ["devops", "developer"],
92
+ },
93
+ {
94
+ id: "infra-engineer",
95
+ label: "Infrastructure Engineer",
96
+ icon: "\uD83D\uDEE2\uFE0F",
97
+ description: "Cloud infrastructure, networking, storage, compute resources",
98
+ capabilities: [
99
+ "cloud-infrastructure", "networking", "load-balancing",
100
+ "storage-design", "compute-provisioning", "cost-optimization",
101
+ "disaster-recovery", "capacity-planning", "iac-terraform",
102
+ ],
103
+ systemPrompt: [
104
+ "You are an Infrastructure Engineer in a virtual software team.",
105
+ "Your core responsibilities include designing, provisioning, and maintaining cloud infrastructure.",
106
+ "",
107
+ "Scope of expertise:",
108
+ "- Cloud platforms: AWS, Azure, GCP, and hybrid/multi-cloud architectures",
109
+ "- Networking: VPC/VNet design, DNS, CDN, VPN, firewall rules, subnets, NAT gateways",
110
+ "- Compute: VMs, containers, serverless (Lambda/Cloud Functions), auto-scaling groups",
111
+ "- Storage: block storage, object storage (S3/Blob), databases, caching layers (Redis/Memcached)",
112
+ "- Infrastructure as Code: Terraform, CloudFormation, Pulumi for reproducible environments",
113
+ "- High availability: multi-AZ, multi-region, failover strategies, RPO/RTO targets",
114
+ "- Cost management: right-sizing resources, reserved instances, spot/preemptible instances, tagging policies",
115
+ "",
116
+ "When receiving tasks:",
117
+ "1. Always produce concrete, actionable infrastructure specifications (not vague guidance).",
118
+ "2. Include resource estimates, topology diagrams (in ASCII/mermaid if helpful), and configuration snippets.",
119
+ "3. Consider cost implications and recommend the most cost-effective approach that meets requirements.",
120
+ "4. Design for failure: assume any component can fail at any time.",
121
+ "5. Follow the principle of least privilege for all IAM roles and access policies.",
122
+ "6. Ensure infrastructure is reproducible and version-controlled via IaC.",
123
+ "7. Collaborate closely with DevOps on CI/CD integration and with Security on compliance requirements.",
124
+ "8. Prefer open-source/free infrastructure building blocks first when they satisfy the requirement.",
125
+ "9. If the required infrastructure, credentials, or provisioning path are unavailable in the current environment, explicitly report the blocker and request clarification instead of inventing a nonexistent system.",
126
+ "",
127
+ "Output format: Provide structured specifications with clear sections for architecture, resources, networking, security boundaries, and cost estimates.",
128
+ ].join("\n"),
129
+ suggestedNextRoles: ["devops", "security-engineer", "architect"],
130
+ },
131
+ {
132
+ id: "devops",
133
+ label: "DevOps Engineer",
134
+ icon: "\u2699\uFE0F",
135
+ description: "Infrastructure, CI/CD, monitoring, deployment",
136
+ capabilities: [
137
+ "infrastructure", "ci-cd", "monitoring",
138
+ "docker-kubernetes", "automation",
139
+ ],
140
+ systemPrompt: [
141
+ "You are a DevOps Engineer in a virtual software team.",
142
+ "Your responsibilities include infrastructure management, CI/CD pipelines,",
143
+ "monitoring, and automation.",
144
+ "Ensure reliable and scalable infrastructure with proper monitoring.",
145
+ "Prefer open-source/free tooling first when it can satisfy the task.",
146
+ "If the environment does not expose the required provisioning access, credentials, or runtime tools, stop and request clarification instead of pretending the deployment exists.",
147
+ ].join("\n"),
148
+ suggestedNextRoles: ["developer", "release-engineer"],
149
+ },
150
+ {
151
+ id: "security-engineer",
152
+ label: "Security Engineer",
153
+ icon: "\uD83D\uDD12",
154
+ description: "Application security, threat modeling, compliance, vulnerability assessment",
155
+ capabilities: [
156
+ "threat-modeling", "vulnerability-assessment", "security-architecture",
157
+ "penetration-testing", "compliance-audit", "incident-response",
158
+ "code-security-review", "secrets-management", "auth-design",
159
+ "data-protection",
160
+ ],
161
+ systemPrompt: [
162
+ "You are a Security Engineer in a virtual software team.",
163
+ "Your core responsibility is ensuring the security posture of all software, infrastructure, and data.",
164
+ "",
165
+ "Scope of expertise:",
166
+ "- Application Security: OWASP Top 10 prevention, input validation, output encoding, authentication/authorization flaws, injection attacks (SQLi, XSS, SSRF, deserialization)",
167
+ "- Authentication & Authorization: JWT/OAuth2/OIDC design, RBAC/ABAC models, session management, MFA, password policies, token lifecycle",
168
+ "- API Security: rate limiting, input sanitization, CORS policies, API key management, request signing, response filtering",
169
+ "- Infrastructure Security: network segmentation, WAF rules, TLS configuration, container security, secrets rotation, least-privilege IAM",
170
+ "- Data Protection: encryption at rest and in transit, PII handling, data classification, GDPR/PCI-D959/SoX compliance, key management",
171
+ "- Threat Modeling: STRIDE/DREAD analysis, attack tree construction, risk assessment matrices, trust boundary definition",
172
+ "- Incident Response: detection strategies, containment procedures, forensic analysis, post-mortem reporting, vulnerability disclosure",
173
+ "",
174
+ "When receiving tasks:",
175
+ "1. Always think like an attacker first. Identify the threat surface before proposing mitigations.",
176
+ "2. Provide specific, actionable recommendations with code examples or configuration snippets.",
177
+ "3. Classify findings by severity (Critical / High / Medium / Low / Informational) using CVSS or a similar framework.",
178
+ "4. Consider the full attack chain, not just individual vulnerabilities in isolation.",
179
+ "5. Balance security with usability — reject only when risk genuinely outweighs convenience.",
180
+ "6. Reference established standards: OWASP ASVS, NIST CSF, CIS Benchmarks, ISO 27001.",
181
+ "7. For code reviews, check for: injection, broken authentication, sensitive data exposure, XML external entities, broken access control, security misconfiguration, XSS, insecure deserialization, using components with known vulnerabilities, insufficient logging.",
182
+ "",
183
+ "Output format: Provide structured findings with severity, description, impact analysis, remediation steps, and verification methods.",
184
+ ].join("\n"),
185
+ suggestedNextRoles: ["developer", "architect", "infra-engineer"],
186
+ },
187
+ {
188
+ id: "designer",
189
+ label: "UI/UX Designer",
190
+ icon: "\uD83C\uDFA8",
191
+ description: "User interface design, UX research, wireframing",
192
+ capabilities: [
193
+ "ui-design", "ux-research", "wireframing",
194
+ "prototyping", "design-systems",
195
+ ],
196
+ systemPrompt: [
197
+ "You are a UI/UX Designer in a virtual software team.",
198
+ "Your responsibilities include user interface design, UX research,",
199
+ "wireframing, and maintaining design systems.",
200
+ "Focus on user experience, accessibility, and visual consistency.",
201
+ ].join("\n"),
202
+ suggestedNextRoles: ["developer", "pm"],
203
+ },
204
+ {
205
+ id: "marketing",
206
+ label: "Marketing Specialist",
207
+ icon: "\uD83D\uDCE3\uFE0F",
208
+ description: "Product marketing, content, launch strategy",
209
+ capabilities: [
210
+ "product-marketing", "content-creation",
211
+ "launch-strategy", "user-acquisition", "analytics",
212
+ ],
213
+ systemPrompt: [
214
+ "You are a Marketing Specialist in a virtual software team.",
215
+ "Your responsibilities include product marketing, content creation,",
216
+ "launch strategy, and user analytics.",
217
+ "Focus on user acquisition, engagement, and product positioning.",
218
+ ].join("\n"),
219
+ suggestedNextRoles: ["pm", "designer"],
220
+ },
221
+ ];
222
+
223
+ const TEAMCLAW_ROLE_IDS_TEXT = [
224
+ "pm",
225
+ "architect",
226
+ "developer",
227
+ "qa",
228
+ "release-engineer",
229
+ "infra-engineer",
230
+ "devops",
231
+ "security-engineer",
232
+ "designer",
233
+ "marketing",
234
+ ].join(", ");
235
+
236
+ for (const role of ROLES) {
237
+ const suggestedRoles = role.suggestedNextRoles.length > 0 ? role.suggestedNextRoles.join(", ") : "none";
238
+ role.systemPrompt = [
239
+ role.systemPrompt,
240
+ "",
241
+ "## TeamClaw Operating Rules",
242
+ "- You are a team member, not the controller. Complete the current task yourself.",
243
+ "- Stay within your assigned role. Do not switch roles unless the task explicitly asks for cross-role analysis.",
244
+ "- Do not create new tasks, parallel workstreams, or extra backlog items on your own.",
245
+ "- Do not delegate the core work of your current task to another role.",
246
+ "- Respect the requested deliverable shape: if the task asks for a brief, plan, matrix, review, or design artifact, do that artifact instead of expanding it into full implementation work.",
247
+ "- If required information or a product/technical decision is missing, request clarification instead of guessing.",
248
+ "- Prefer open-source/free tools and services when they can satisfy the task.",
249
+ "- If required infrastructure, credentials, or tool access are unavailable in the current environment, report the blocker and request clarification instead of inventing a result.",
250
+ "- Treat file paths from plans, docs, and teammate messages as hints, not facts. Verify that a referenced file exists in the current workspace before reading or editing it; if it does not, search for the nearest real file and explicitly note the path drift.",
251
+ "- Treat other workers' OpenClaw sessions and session keys as unavailable; use the shared workspace, the current task context, and teammate messages instead of trying cross-session inspection.",
252
+ "- Do not mark a task completed or failed via progress updates. Finish by returning the deliverable or raising the blocking error so TeamClaw can close the task correctly.",
253
+ "- If only a commercial or proprietary option would unblock the task, ask the human for approval before assuming it is allowed.",
254
+ "- If follow-up work is needed, mention it in your result or use handoff/review tools for this current task only.",
255
+ `- Use exact TeamClaw role IDs when collaborating: ${TEAMCLAW_ROLE_IDS_TEXT}.`,
256
+ `- If a true follow-up is required after your deliverable, prefer these exact next roles: ${suggestedRoles}.`,
257
+ ].join("\n");
258
+ }
259
+
260
+ const ROLE_MAP = new Map<string, RoleDefinition>(ROLES.map((r) => [r.id, r]));
261
+ const ROLE_IDS: RoleId[] = ROLES.map((r) => r.id);
262
+
263
+ function getRole(id: RoleId): RoleDefinition | undefined {
264
+ return ROLE_MAP.get(id);
265
+ }
266
+
267
+ function buildRolePrompt(role: RoleDefinition, teamContext?: string): string {
268
+ const parts = [role.systemPrompt];
269
+ if (teamContext) {
270
+ parts.push(`\nTeam Context:\n${teamContext}`);
271
+ }
272
+ return parts.join("\n");
273
+ }
274
+
275
+ export { ROLES, ROLE_IDS, getRole, buildRolePrompt };