@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.
- package/README.md +37 -0
- package/api.ts +10 -0
- package/index.ts +246 -0
- package/openclaw.plugin.json +41 -0
- package/package.json +63 -0
- package/src/config.ts +297 -0
- package/src/controller/controller-service.ts +197 -0
- package/src/controller/controller-tools.ts +224 -0
- package/src/controller/http-server.ts +1946 -0
- package/src/controller/local-worker-manager.ts +531 -0
- package/src/controller/message-router.ts +62 -0
- package/src/controller/prompt-injector.ts +116 -0
- package/src/controller/task-router.ts +97 -0
- package/src/controller/websocket.ts +63 -0
- package/src/controller/worker-provisioning.ts +1286 -0
- package/src/discovery.ts +101 -0
- package/src/git-collaboration.ts +690 -0
- package/src/identity.ts +149 -0
- package/src/openclaw-workspace.ts +101 -0
- package/src/protocol.ts +88 -0
- package/src/roles.ts +275 -0
- package/src/state.ts +118 -0
- package/src/task-executor.ts +478 -0
- package/src/types.ts +469 -0
- package/src/ui/app.js +1400 -0
- package/src/ui/index.html +207 -0
- package/src/ui/style.css +1281 -0
- package/src/worker/http-handler.ts +136 -0
- package/src/worker/message-queue.ts +31 -0
- package/src/worker/prompt-injector.ts +72 -0
- package/src/worker/tools.ts +318 -0
- package/src/worker/worker-service.ts +194 -0
- package/src/workspace-browser.ts +312 -0
- package/tsconfig.json +22 -0
package/README.md
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# @teamclaws/teamclaw
|
|
2
|
+
|
|
3
|
+
TeamClaw is an **OpenClaw plugin** that turns multiple roles into a collaborative virtual software team.
|
|
4
|
+
|
|
5
|
+
It supports:
|
|
6
|
+
|
|
7
|
+
- `controller` / `worker` modes
|
|
8
|
+
- single-instance `localRoles`
|
|
9
|
+
- clarifications, workspace browsing, and Web UI
|
|
10
|
+
- Git-based collaboration
|
|
11
|
+
- on-demand worker provisioning with `process`, `docker`, and `kubernetes`
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
If you already have OpenClaw installed, add the plugin with:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
openclaw plugins install @teamclaws/teamclaw
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Then enable and configure TeamClaw in your `openclaw.json`.
|
|
22
|
+
|
|
23
|
+
## Recommended first setup
|
|
24
|
+
|
|
25
|
+
For a first-time setup, the safest path is:
|
|
26
|
+
|
|
27
|
+
1. Start with a single machine and `controller + localRoles`
|
|
28
|
+
2. Validate the workflow with a small smoke-test task
|
|
29
|
+
3. Expand to distributed or on-demand workers after the basics are working
|
|
30
|
+
|
|
31
|
+
## Documentation
|
|
32
|
+
|
|
33
|
+
For complete setup and architecture details, see:
|
|
34
|
+
|
|
35
|
+
- Installation guide: <https://github.com/topcheer/teamclaw/blob/main/INSTALL.md>
|
|
36
|
+
- Repository overview: <https://github.com/topcheer/teamclaw/blob/main/README.md>
|
|
37
|
+
- Design notes: <https://github.com/topcheer/teamclaw/blob/main/DESIGN.md>
|
package/api.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export { emptyPluginConfigSchema } from "openclaw/plugin-sdk/core";
|
|
2
|
+
export type {
|
|
3
|
+
AnyAgentTool,
|
|
4
|
+
OpenClawPluginApi,
|
|
5
|
+
OpenClawPluginService,
|
|
6
|
+
OpenClawPluginServiceContext,
|
|
7
|
+
OpenClawPluginToolContext,
|
|
8
|
+
OpenClawPluginToolFactory,
|
|
9
|
+
PluginLogger,
|
|
10
|
+
} from "openclaw/plugin-sdk/core";
|
package/index.ts
ADDED
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import type { OpenClawPluginApi } from "./api.js";
|
|
2
|
+
import { parsePluginConfig } from "./src/types.js";
|
|
3
|
+
import type { TaskExecutionEventInput, WorkerIdentity } from "./src/types.js";
|
|
4
|
+
import { buildConfigSchema } from "./src/config.js";
|
|
5
|
+
import { loadTeamState } from "./src/state.js";
|
|
6
|
+
import { createRoleTaskExecutor } from "./src/task-executor.js";
|
|
7
|
+
import { createWorkerService } from "./src/worker/worker-service.js";
|
|
8
|
+
import { createWorkerPromptInjector } from "./src/worker/prompt-injector.js";
|
|
9
|
+
import { createWorkerTools } from "./src/worker/tools.js";
|
|
10
|
+
import { MessageQueue } from "./src/worker/message-queue.js";
|
|
11
|
+
import { createControllerService } from "./src/controller/controller-service.js";
|
|
12
|
+
import { LocalWorkerManager } from "./src/controller/local-worker-manager.js";
|
|
13
|
+
import { createControllerPromptInjector } from "./src/controller/prompt-injector.js";
|
|
14
|
+
import { createControllerTools } from "./src/controller/controller-tools.js";
|
|
15
|
+
import { publishWorkerRepo, syncWorkerRepo } from "./src/git-collaboration.js";
|
|
16
|
+
|
|
17
|
+
const plugin = {
|
|
18
|
+
id: "teamclaw",
|
|
19
|
+
name: "TeamClaw",
|
|
20
|
+
description:
|
|
21
|
+
"Virtual team collaboration - multiple OpenClaw instances form a virtual software company with role-based task routing.",
|
|
22
|
+
configSchema: buildConfigSchema(),
|
|
23
|
+
register(api: OpenClawPluginApi) {
|
|
24
|
+
const config = parsePluginConfig(api.pluginConfig as Record<string, unknown>);
|
|
25
|
+
const logger = api.logger;
|
|
26
|
+
|
|
27
|
+
if (config.mode === "controller") {
|
|
28
|
+
registerController(api, config);
|
|
29
|
+
} else {
|
|
30
|
+
registerWorker(api, config);
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export default plugin;
|
|
36
|
+
|
|
37
|
+
function registerController(api: OpenClawPluginApi, config: ReturnType<typeof parsePluginConfig>) {
|
|
38
|
+
const logger = api.logger;
|
|
39
|
+
const localWorkerManager = new LocalWorkerManager({
|
|
40
|
+
config,
|
|
41
|
+
logger,
|
|
42
|
+
runtime: api.runtime,
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// Service (starts HTTP server + mDNS + WebSocket)
|
|
46
|
+
api.registerService(createControllerService({ config, logger, runtime: api.runtime, localWorkerManager }));
|
|
47
|
+
|
|
48
|
+
// Prompt injection
|
|
49
|
+
api.on("before_prompt_build", async (_event: unknown, ctx: { sessionKey?: string | null }) => {
|
|
50
|
+
const localIdentity = localWorkerManager.getIdentityForSession(ctx.sessionKey);
|
|
51
|
+
const localMessageQueue = localWorkerManager.getMessageQueueForSession(ctx.sessionKey);
|
|
52
|
+
if (localIdentity && localMessageQueue) {
|
|
53
|
+
const injector = createWorkerPromptInjector(
|
|
54
|
+
{ ...config, role: localIdentity.role },
|
|
55
|
+
() => localIdentity,
|
|
56
|
+
localMessageQueue,
|
|
57
|
+
);
|
|
58
|
+
return injector() ?? {};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const state = await loadTeamState(config.teamName);
|
|
62
|
+
const injector = createControllerPromptInjector({
|
|
63
|
+
config,
|
|
64
|
+
getTeamState: () => state,
|
|
65
|
+
});
|
|
66
|
+
return injector() ?? {};
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// Tools - register all controller tools via factory returning an array
|
|
70
|
+
const controllerUrl = `http://localhost:${config.port}`;
|
|
71
|
+
api.registerTool((ctx: { sessionKey?: string | null }) => {
|
|
72
|
+
const localIdentity = localWorkerManager.getIdentityForSession(ctx.sessionKey);
|
|
73
|
+
if (localIdentity) {
|
|
74
|
+
return createWorkerTools({
|
|
75
|
+
config: { ...config, role: localIdentity.role },
|
|
76
|
+
getIdentity: () => localIdentity,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return createControllerTools({
|
|
81
|
+
config,
|
|
82
|
+
controllerUrl,
|
|
83
|
+
getTeamState: () => null,
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function registerWorker(api: OpenClawPluginApi, config: ReturnType<typeof parsePluginConfig>) {
|
|
89
|
+
const logger = api.logger;
|
|
90
|
+
const messageQueue = new MessageQueue();
|
|
91
|
+
let currentControllerUrl: string | null = null;
|
|
92
|
+
let currentWorkerId: string | null = null;
|
|
93
|
+
|
|
94
|
+
function getIdentity(): WorkerIdentity | null {
|
|
95
|
+
if (!currentWorkerId || !currentControllerUrl) return null;
|
|
96
|
+
return {
|
|
97
|
+
workerId: currentWorkerId,
|
|
98
|
+
role: config.role,
|
|
99
|
+
controllerUrl: currentControllerUrl,
|
|
100
|
+
registeredAt: Date.now(),
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async function reportExecutionEvent(taskId: string, event: TaskExecutionEventInput): Promise<void> {
|
|
105
|
+
if (!currentControllerUrl) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
try {
|
|
110
|
+
const res = await fetch(`${currentControllerUrl}/api/v1/tasks/${taskId}/execution`, {
|
|
111
|
+
method: "POST",
|
|
112
|
+
headers: { "Content-Type": "application/json" },
|
|
113
|
+
body: JSON.stringify({
|
|
114
|
+
...event,
|
|
115
|
+
workerId: currentWorkerId ?? undefined,
|
|
116
|
+
role: config.role,
|
|
117
|
+
}),
|
|
118
|
+
});
|
|
119
|
+
if (!res.ok) {
|
|
120
|
+
logger.warn(`Worker: failed to record execution event for ${taskId} (${res.status})`);
|
|
121
|
+
}
|
|
122
|
+
} catch (err) {
|
|
123
|
+
logger.warn(`Worker: failed to POST execution event for ${taskId}: ${String(err)}`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const getWorkerSessionKey = (taskId: string) => `teamclaw-task-${taskId}`;
|
|
128
|
+
|
|
129
|
+
const taskExecutor = createRoleTaskExecutor({
|
|
130
|
+
runtime: api.runtime,
|
|
131
|
+
logger,
|
|
132
|
+
role: config.role,
|
|
133
|
+
taskTimeoutMs: config.taskTimeoutMs,
|
|
134
|
+
getSessionKey: getWorkerSessionKey,
|
|
135
|
+
getIdempotencyKey: (taskId) => `teamclaw-${taskId}`,
|
|
136
|
+
reportExecutionEvent,
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
// Service
|
|
140
|
+
api.registerService(
|
|
141
|
+
createWorkerService({
|
|
142
|
+
config,
|
|
143
|
+
logger,
|
|
144
|
+
onIdentityEstablished: (identity) => {
|
|
145
|
+
currentControllerUrl = identity.controllerUrl;
|
|
146
|
+
currentWorkerId = identity.workerId;
|
|
147
|
+
},
|
|
148
|
+
prepareTaskAssignment: async (assignment) => {
|
|
149
|
+
if (!assignment.repo?.enabled || !currentControllerUrl) {
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
await reportExecutionEvent(assignment.taskId, {
|
|
154
|
+
type: "lifecycle",
|
|
155
|
+
phase: "repo_sync_started",
|
|
156
|
+
source: "worker",
|
|
157
|
+
status: "running",
|
|
158
|
+
message: `Preparing ${assignment.repo.mode} git workspace sync before task execution.`,
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
try {
|
|
162
|
+
const syncResult = await syncWorkerRepo(config, logger, currentControllerUrl, assignment.repo);
|
|
163
|
+
await reportExecutionEvent(assignment.taskId, {
|
|
164
|
+
type: "lifecycle",
|
|
165
|
+
phase: "repo_sync_completed",
|
|
166
|
+
source: "worker",
|
|
167
|
+
status: "running",
|
|
168
|
+
message: syncResult.message,
|
|
169
|
+
});
|
|
170
|
+
} catch (err) {
|
|
171
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
172
|
+
await reportExecutionEvent(assignment.taskId, {
|
|
173
|
+
type: "error",
|
|
174
|
+
phase: "repo_sync_failed",
|
|
175
|
+
source: "worker",
|
|
176
|
+
status: "running",
|
|
177
|
+
message,
|
|
178
|
+
});
|
|
179
|
+
throw err;
|
|
180
|
+
}
|
|
181
|
+
},
|
|
182
|
+
publishTaskAssignment: async (assignment) => {
|
|
183
|
+
if (!assignment.repo?.enabled || !currentControllerUrl || !currentWorkerId) {
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
await reportExecutionEvent(assignment.taskId, {
|
|
188
|
+
type: "lifecycle",
|
|
189
|
+
phase: "repo_publish_started",
|
|
190
|
+
source: "worker",
|
|
191
|
+
status: "running",
|
|
192
|
+
message: `Publishing task changes through ${assignment.repo.mode} git collaboration.`,
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
try {
|
|
196
|
+
const publishResult = await publishWorkerRepo(config, logger, currentControllerUrl, assignment.repo, {
|
|
197
|
+
taskId: assignment.taskId,
|
|
198
|
+
workerId: currentWorkerId,
|
|
199
|
+
role: config.role,
|
|
200
|
+
});
|
|
201
|
+
await reportExecutionEvent(assignment.taskId, {
|
|
202
|
+
type: "lifecycle",
|
|
203
|
+
phase: publishResult.published ? "repo_publish_completed" : "repo_publish_skipped",
|
|
204
|
+
source: "worker",
|
|
205
|
+
status: "running",
|
|
206
|
+
message: publishResult.message,
|
|
207
|
+
});
|
|
208
|
+
} catch (err) {
|
|
209
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
210
|
+
await reportExecutionEvent(assignment.taskId, {
|
|
211
|
+
type: "error",
|
|
212
|
+
phase: "repo_publish_failed",
|
|
213
|
+
source: "worker",
|
|
214
|
+
status: "running",
|
|
215
|
+
message,
|
|
216
|
+
});
|
|
217
|
+
throw err;
|
|
218
|
+
}
|
|
219
|
+
},
|
|
220
|
+
taskExecutor,
|
|
221
|
+
cancelTaskExecution: async (taskId) => {
|
|
222
|
+
const sessionKey = getWorkerSessionKey(taskId);
|
|
223
|
+
try {
|
|
224
|
+
await api.runtime.subagent.deleteSession({ sessionKey });
|
|
225
|
+
logger.info(`Worker: cancelled subagent session ${sessionKey} for task ${taskId}`);
|
|
226
|
+
return true;
|
|
227
|
+
} catch (err) {
|
|
228
|
+
logger.warn(`Worker: failed to cancel session ${sessionKey} for task ${taskId}: ${String(err)}`);
|
|
229
|
+
return false;
|
|
230
|
+
}
|
|
231
|
+
},
|
|
232
|
+
messageQueue,
|
|
233
|
+
}),
|
|
234
|
+
);
|
|
235
|
+
|
|
236
|
+
// Prompt injection
|
|
237
|
+
api.on("before_prompt_build", async () => {
|
|
238
|
+
const injector = createWorkerPromptInjector(config, getIdentity, messageQueue);
|
|
239
|
+
return injector() ?? {};
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
// Tools - register all worker tools via factory returning an array
|
|
243
|
+
api.registerTool(() => {
|
|
244
|
+
return createWorkerTools({ config, getIdentity });
|
|
245
|
+
});
|
|
246
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "teamclaw",
|
|
3
|
+
"name": "TeamClaw",
|
|
4
|
+
"description": "Virtual team collaboration plugin - multiple OpenClaw instances form a virtual software company with role-based task routing.",
|
|
5
|
+
"configSchema": {
|
|
6
|
+
"type": "object",
|
|
7
|
+
"properties": {
|
|
8
|
+
"mode": {
|
|
9
|
+
"type": "string",
|
|
10
|
+
"enum": ["controller", "worker"],
|
|
11
|
+
"default": "worker",
|
|
12
|
+
"description": "Plugin mode: controller manages the team, worker executes tasks"
|
|
13
|
+
},
|
|
14
|
+
"port": {
|
|
15
|
+
"type": "number",
|
|
16
|
+
"default": 9527,
|
|
17
|
+
"description": "HTTP server port for this instance"
|
|
18
|
+
},
|
|
19
|
+
"role": {
|
|
20
|
+
"type": "string",
|
|
21
|
+
"default": "developer",
|
|
22
|
+
"description": "Worker role (only used in worker mode)"
|
|
23
|
+
},
|
|
24
|
+
"controllerUrl": {
|
|
25
|
+
"type": "string",
|
|
26
|
+
"default": "",
|
|
27
|
+
"description": "Manual controller URL fallback (used when mDNS discovery fails)"
|
|
28
|
+
},
|
|
29
|
+
"teamName": {
|
|
30
|
+
"type": "string",
|
|
31
|
+
"default": "default",
|
|
32
|
+
"description": "Team name for mDNS identification"
|
|
33
|
+
},
|
|
34
|
+
"heartbeatIntervalMs": {
|
|
35
|
+
"type": "number",
|
|
36
|
+
"default": 10000,
|
|
37
|
+
"description": "Heartbeat interval in milliseconds"
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@teamclaws/teamclaw",
|
|
3
|
+
"version": "2026.3.21",
|
|
4
|
+
"description": "OpenClaw virtual software team orchestration plugin",
|
|
5
|
+
"private": false,
|
|
6
|
+
"keywords": [
|
|
7
|
+
"openclaw",
|
|
8
|
+
"plugin",
|
|
9
|
+
"teamclaw",
|
|
10
|
+
"multi-agent",
|
|
11
|
+
"orchestration"
|
|
12
|
+
],
|
|
13
|
+
"homepage": "https://github.com/topcheer/teamclaw/tree/main/src#readme",
|
|
14
|
+
"bugs": {
|
|
15
|
+
"url": "https://github.com/topcheer/teamclaw/issues"
|
|
16
|
+
},
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "git+https://github.com/topcheer/teamclaw.git",
|
|
20
|
+
"directory": "src"
|
|
21
|
+
},
|
|
22
|
+
"type": "module",
|
|
23
|
+
"files": [
|
|
24
|
+
"README.md",
|
|
25
|
+
"api.ts",
|
|
26
|
+
"index.ts",
|
|
27
|
+
"openclaw.plugin.json",
|
|
28
|
+
"src/",
|
|
29
|
+
"tsconfig.json"
|
|
30
|
+
],
|
|
31
|
+
"scripts": {
|
|
32
|
+
"pack:dry-run": "npm pack --dry-run --json --ignore-scripts",
|
|
33
|
+
"release:check": "node ../scripts/teamclaw-package-check.mjs src"
|
|
34
|
+
},
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"@sinclair/typebox": "0.34.48",
|
|
37
|
+
"bonjour-service": "^1.3.0",
|
|
38
|
+
"json5": "^2.2.3",
|
|
39
|
+
"ws": "^8.19.0"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"typescript": "^5.9.0"
|
|
43
|
+
},
|
|
44
|
+
"peerDependencies": {
|
|
45
|
+
"openclaw": ">=2026.3.14"
|
|
46
|
+
},
|
|
47
|
+
"peerDependenciesMeta": {
|
|
48
|
+
"openclaw": {
|
|
49
|
+
"optional": true
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
"publishConfig": {
|
|
53
|
+
"access": "public"
|
|
54
|
+
},
|
|
55
|
+
"openclaw": {
|
|
56
|
+
"extensions": [
|
|
57
|
+
"./index.ts"
|
|
58
|
+
],
|
|
59
|
+
"release": {
|
|
60
|
+
"publishToNpm": true
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
package/src/config.ts
ADDED
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
import type { PluginConfig } from "./types.js";
|
|
2
|
+
import { parsePluginConfig } from "./types.js";
|
|
3
|
+
import { ROLE_IDS } from "./roles.js";
|
|
4
|
+
|
|
5
|
+
function buildConfigSchema() {
|
|
6
|
+
return {
|
|
7
|
+
jsonSchema: {
|
|
8
|
+
type: "object" as const,
|
|
9
|
+
properties: {
|
|
10
|
+
mode: {
|
|
11
|
+
type: "string" as const,
|
|
12
|
+
enum: ["controller", "worker"],
|
|
13
|
+
default: "worker",
|
|
14
|
+
description: "Plugin mode: controller manages the team, worker executes tasks",
|
|
15
|
+
},
|
|
16
|
+
port: {
|
|
17
|
+
type: "number" as const,
|
|
18
|
+
default: 9527,
|
|
19
|
+
description: "HTTP server port for this instance",
|
|
20
|
+
},
|
|
21
|
+
role: {
|
|
22
|
+
type: "string" as const,
|
|
23
|
+
default: "developer",
|
|
24
|
+
description: "Worker role (only used in worker mode)",
|
|
25
|
+
},
|
|
26
|
+
controllerUrl: {
|
|
27
|
+
type: "string" as const,
|
|
28
|
+
default: "",
|
|
29
|
+
description: "Manual controller URL fallback (used when mDNS discovery fails)",
|
|
30
|
+
},
|
|
31
|
+
teamName: {
|
|
32
|
+
type: "string" as const,
|
|
33
|
+
default: "default",
|
|
34
|
+
description: "Team name for mDNS identification",
|
|
35
|
+
},
|
|
36
|
+
heartbeatIntervalMs: {
|
|
37
|
+
type: "number" as const,
|
|
38
|
+
default: 10000,
|
|
39
|
+
description: "Heartbeat interval in milliseconds",
|
|
40
|
+
},
|
|
41
|
+
taskTimeoutMs: {
|
|
42
|
+
type: "number" as const,
|
|
43
|
+
default: 1800000,
|
|
44
|
+
description: "Maximum time in milliseconds to wait for a role task to finish",
|
|
45
|
+
},
|
|
46
|
+
gitEnabled: {
|
|
47
|
+
type: "boolean" as const,
|
|
48
|
+
default: true,
|
|
49
|
+
description: "Enable TeamClaw git-backed workspace collaboration",
|
|
50
|
+
},
|
|
51
|
+
gitRemoteUrl: {
|
|
52
|
+
type: "string" as const,
|
|
53
|
+
default: "",
|
|
54
|
+
description: "Optional remote repository URL for distributed worker clone/pull/push",
|
|
55
|
+
},
|
|
56
|
+
gitDefaultBranch: {
|
|
57
|
+
type: "string" as const,
|
|
58
|
+
default: "main",
|
|
59
|
+
description: "Default branch name for the shared TeamClaw workspace repository",
|
|
60
|
+
},
|
|
61
|
+
gitAuthorName: {
|
|
62
|
+
type: "string" as const,
|
|
63
|
+
default: "TeamClaw",
|
|
64
|
+
description: "Git author name used for TeamClaw-managed workspace commits",
|
|
65
|
+
},
|
|
66
|
+
gitAuthorEmail: {
|
|
67
|
+
type: "string" as const,
|
|
68
|
+
default: "teamclaw@local",
|
|
69
|
+
description: "Git author email used for TeamClaw-managed workspace commits",
|
|
70
|
+
},
|
|
71
|
+
localRoles: {
|
|
72
|
+
type: "array" as const,
|
|
73
|
+
default: [],
|
|
74
|
+
description: "Controller-local roles executed in this same OpenClaw instance",
|
|
75
|
+
items: {
|
|
76
|
+
type: "string" as const,
|
|
77
|
+
enum: ROLE_IDS,
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
workerProvisioningType: {
|
|
81
|
+
type: "string" as const,
|
|
82
|
+
enum: ["none", "process", "docker", "kubernetes"],
|
|
83
|
+
default: "none",
|
|
84
|
+
description: "Controller-only on-demand worker launch backend",
|
|
85
|
+
},
|
|
86
|
+
workerProvisioningControllerUrl: {
|
|
87
|
+
type: "string" as const,
|
|
88
|
+
default: "",
|
|
89
|
+
description: "Controller URL injected into provisioned workers; required for docker/kubernetes",
|
|
90
|
+
},
|
|
91
|
+
workerProvisioningRoles: {
|
|
92
|
+
type: "array" as const,
|
|
93
|
+
default: [],
|
|
94
|
+
description: "Restrict on-demand launches to specific roles; empty means all roles",
|
|
95
|
+
items: {
|
|
96
|
+
type: "string" as const,
|
|
97
|
+
enum: ROLE_IDS,
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
workerProvisioningMinPerRole: {
|
|
101
|
+
type: "number" as const,
|
|
102
|
+
default: 0,
|
|
103
|
+
description: "Minimum number of ready workers to keep warm per role",
|
|
104
|
+
},
|
|
105
|
+
workerProvisioningMaxPerRole: {
|
|
106
|
+
type: "number" as const,
|
|
107
|
+
default: 1,
|
|
108
|
+
description: "Maximum on-demand workers to launch per role",
|
|
109
|
+
},
|
|
110
|
+
workerProvisioningIdleTtlMs: {
|
|
111
|
+
type: "number" as const,
|
|
112
|
+
default: 120000,
|
|
113
|
+
description: "Terminate provisioned idle workers after this many milliseconds",
|
|
114
|
+
},
|
|
115
|
+
workerProvisioningStartupTimeoutMs: {
|
|
116
|
+
type: "number" as const,
|
|
117
|
+
default: 120000,
|
|
118
|
+
description: "Fail a launch if the worker does not register within this many milliseconds",
|
|
119
|
+
},
|
|
120
|
+
workerProvisioningImage: {
|
|
121
|
+
type: "string" as const,
|
|
122
|
+
default: "",
|
|
123
|
+
description: "Container image used by docker/kubernetes provisioners",
|
|
124
|
+
},
|
|
125
|
+
workerProvisioningPassEnv: {
|
|
126
|
+
type: "array" as const,
|
|
127
|
+
default: [],
|
|
128
|
+
description: "Environment variable names copied from the controller into provisioned workers",
|
|
129
|
+
items: {
|
|
130
|
+
type: "string" as const,
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
workerProvisioningExtraEnv: {
|
|
134
|
+
type: "object" as const,
|
|
135
|
+
default: {},
|
|
136
|
+
description: "Extra environment variables injected into provisioned workers",
|
|
137
|
+
additionalProperties: {
|
|
138
|
+
type: "string" as const,
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
workerProvisioningDockerNetwork: {
|
|
142
|
+
type: "string" as const,
|
|
143
|
+
default: "",
|
|
144
|
+
description: "Optional Docker network name for launched worker containers",
|
|
145
|
+
},
|
|
146
|
+
workerProvisioningDockerMounts: {
|
|
147
|
+
type: "array" as const,
|
|
148
|
+
default: [],
|
|
149
|
+
description: "Optional Docker bind mounts for launched worker containers",
|
|
150
|
+
items: {
|
|
151
|
+
type: "string" as const,
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
workerProvisioningKubernetesNamespace: {
|
|
155
|
+
type: "string" as const,
|
|
156
|
+
default: "default",
|
|
157
|
+
description: "Kubernetes namespace for launched worker pods",
|
|
158
|
+
},
|
|
159
|
+
workerProvisioningKubernetesContext: {
|
|
160
|
+
type: "string" as const,
|
|
161
|
+
default: "",
|
|
162
|
+
description: "Optional kubectl context used by the Kubernetes provisioner",
|
|
163
|
+
},
|
|
164
|
+
workerProvisioningKubernetesServiceAccount: {
|
|
165
|
+
type: "string" as const,
|
|
166
|
+
default: "",
|
|
167
|
+
description: "Optional service account name for launched worker pods",
|
|
168
|
+
},
|
|
169
|
+
workerProvisioningKubernetesLabels: {
|
|
170
|
+
type: "object" as const,
|
|
171
|
+
default: {},
|
|
172
|
+
description: "Extra labels applied to launched worker pods",
|
|
173
|
+
additionalProperties: {
|
|
174
|
+
type: "string" as const,
|
|
175
|
+
},
|
|
176
|
+
},
|
|
177
|
+
workerProvisioningKubernetesAnnotations: {
|
|
178
|
+
type: "object" as const,
|
|
179
|
+
default: {},
|
|
180
|
+
description: "Extra annotations applied to launched worker pods",
|
|
181
|
+
additionalProperties: {
|
|
182
|
+
type: "string" as const,
|
|
183
|
+
},
|
|
184
|
+
},
|
|
185
|
+
},
|
|
186
|
+
},
|
|
187
|
+
uiHints: {
|
|
188
|
+
mode: { label: "Mode", help: "controller manages the team, worker executes tasks" },
|
|
189
|
+
port: { label: "Port", help: "HTTP server port for this instance" },
|
|
190
|
+
role: { label: "Role", help: "Worker role (worker mode only)" },
|
|
191
|
+
controllerUrl: { label: "Controller URL", help: "Manual fallback if mDNS discovery fails" },
|
|
192
|
+
teamName: { label: "Team Name", help: "Team identifier for mDNS" },
|
|
193
|
+
heartbeatIntervalMs: { label: "Heartbeat Interval", help: "In milliseconds, minimum 1000" },
|
|
194
|
+
taskTimeoutMs: {
|
|
195
|
+
label: "Task Timeout",
|
|
196
|
+
help: "Maximum time to wait for a role task to finish before marking it failed (in milliseconds)",
|
|
197
|
+
},
|
|
198
|
+
gitEnabled: {
|
|
199
|
+
label: "Git Collaboration",
|
|
200
|
+
help: "Enable automatic git-backed workspace bootstrapping and worker repo sync",
|
|
201
|
+
},
|
|
202
|
+
gitRemoteUrl: {
|
|
203
|
+
label: "Git Remote URL",
|
|
204
|
+
help: "Optional remote repository URL; when empty, distributed workers use controller-hosted git bundles",
|
|
205
|
+
},
|
|
206
|
+
gitDefaultBranch: {
|
|
207
|
+
label: "Git Default Branch",
|
|
208
|
+
help: "Default branch name for the TeamClaw workspace repository",
|
|
209
|
+
},
|
|
210
|
+
gitAuthorName: {
|
|
211
|
+
label: "Git Author Name",
|
|
212
|
+
help: "Author name for TeamClaw-managed workspace commits",
|
|
213
|
+
},
|
|
214
|
+
gitAuthorEmail: {
|
|
215
|
+
label: "Git Author Email",
|
|
216
|
+
help: "Author email for TeamClaw-managed workspace commits",
|
|
217
|
+
},
|
|
218
|
+
localRoles: {
|
|
219
|
+
label: "Local Roles",
|
|
220
|
+
help: "Controller mode only: run these roles as local virtual workers inside the same OpenClaw instance",
|
|
221
|
+
},
|
|
222
|
+
workerProvisioningType: {
|
|
223
|
+
label: "On-demand Worker Provider",
|
|
224
|
+
help: "Launch missing workers on demand using process, Docker, or Kubernetes",
|
|
225
|
+
},
|
|
226
|
+
workerProvisioningControllerUrl: {
|
|
227
|
+
label: "Provisioned Worker Controller URL",
|
|
228
|
+
help: "URL that launched workers use to call back into the controller",
|
|
229
|
+
},
|
|
230
|
+
workerProvisioningRoles: {
|
|
231
|
+
label: "Provisioned Roles",
|
|
232
|
+
help: "Only launch these roles on demand; leave empty for all roles",
|
|
233
|
+
},
|
|
234
|
+
workerProvisioningMinPerRole: {
|
|
235
|
+
label: "Warm Workers Per Role",
|
|
236
|
+
help: "Minimum idle workers to keep warm per role",
|
|
237
|
+
},
|
|
238
|
+
workerProvisioningMaxPerRole: {
|
|
239
|
+
label: "Max Workers Per Role",
|
|
240
|
+
help: "Maximum concurrent on-demand workers per role",
|
|
241
|
+
},
|
|
242
|
+
workerProvisioningIdleTtlMs: {
|
|
243
|
+
label: "Idle TTL",
|
|
244
|
+
help: "Terminate an idle provisioned worker after this many milliseconds",
|
|
245
|
+
},
|
|
246
|
+
workerProvisioningStartupTimeoutMs: {
|
|
247
|
+
label: "Startup Timeout",
|
|
248
|
+
help: "Fail a launch if the worker does not register in time",
|
|
249
|
+
},
|
|
250
|
+
workerProvisioningImage: {
|
|
251
|
+
label: "Provisioning Image",
|
|
252
|
+
help: "Container image for docker/kubernetes provisioners",
|
|
253
|
+
},
|
|
254
|
+
workerProvisioningPassEnv: {
|
|
255
|
+
label: "Pass-through Env",
|
|
256
|
+
help: "Environment variable names copied from controller to provisioned workers",
|
|
257
|
+
},
|
|
258
|
+
workerProvisioningExtraEnv: {
|
|
259
|
+
label: "Extra Env",
|
|
260
|
+
help: "Static environment variables injected into provisioned workers",
|
|
261
|
+
},
|
|
262
|
+
workerProvisioningDockerNetwork: {
|
|
263
|
+
label: "Docker Network",
|
|
264
|
+
help: "Optional Docker network for launched worker containers",
|
|
265
|
+
},
|
|
266
|
+
workerProvisioningDockerMounts: {
|
|
267
|
+
label: "Docker Mounts",
|
|
268
|
+
help: "Optional Docker bind mounts for launched worker containers",
|
|
269
|
+
},
|
|
270
|
+
workerProvisioningKubernetesNamespace: {
|
|
271
|
+
label: "Kubernetes Namespace",
|
|
272
|
+
help: "Namespace for launched worker pods",
|
|
273
|
+
},
|
|
274
|
+
workerProvisioningKubernetesContext: {
|
|
275
|
+
label: "Kubernetes Context",
|
|
276
|
+
help: "Optional kubectl context for the Kubernetes provider",
|
|
277
|
+
},
|
|
278
|
+
workerProvisioningKubernetesServiceAccount: {
|
|
279
|
+
label: "Kubernetes Service Account",
|
|
280
|
+
help: "Optional service account for launched worker pods",
|
|
281
|
+
},
|
|
282
|
+
workerProvisioningKubernetesLabels: {
|
|
283
|
+
label: "Kubernetes Labels",
|
|
284
|
+
help: "Extra labels applied to launched worker pods",
|
|
285
|
+
},
|
|
286
|
+
workerProvisioningKubernetesAnnotations: {
|
|
287
|
+
label: "Kubernetes Annotations",
|
|
288
|
+
help: "Extra annotations applied to launched worker pods",
|
|
289
|
+
},
|
|
290
|
+
},
|
|
291
|
+
parse(raw: unknown): PluginConfig {
|
|
292
|
+
return parsePluginConfig(raw as Record<string, unknown>);
|
|
293
|
+
},
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
export { buildConfigSchema };
|