clawchef 0.1.7 → 0.1.9
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/AGENTS.md +159 -0
- package/README.md +49 -4
- package/dist/api.d.ts +2 -1
- package/dist/api.js +1 -0
- package/dist/cli.js +9 -0
- package/dist/openclaw/command-provider.d.ts +3 -2
- package/dist/openclaw/command-provider.js +85 -2
- package/dist/openclaw/mock-provider.d.ts +3 -2
- package/dist/openclaw/mock-provider.js +4 -1
- package/dist/openclaw/provider.d.ts +3 -2
- package/dist/openclaw/remote-provider.d.ts +3 -2
- package/dist/openclaw/remote-provider.js +12 -2
- package/dist/orchestrator.js +101 -8
- package/dist/recipe.js +26 -0
- package/dist/schema.d.ts +124 -6
- package/dist/schema.js +23 -0
- package/dist/types.d.ts +11 -0
- package/package.json +1 -1
- package/recipes/sample.yaml +7 -0
- package/src/api.ts +3 -1
- package/src/cli.ts +11 -1
- package/src/openclaw/command-provider.ts +109 -3
- package/src/openclaw/mock-provider.ts +11 -2
- package/src/openclaw/provider.ts +3 -2
- package/src/openclaw/remote-provider.ts +19 -2
- package/src/orchestrator.ts +108 -8
- package/src/recipe.ts +31 -0
- package/src/schema.ts +25 -0
- package/src/types.ts +12 -0
package/AGENTS.md
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# AGENTS Guide for `clawchef`
|
|
2
|
+
|
|
3
|
+
This file is for coding agents operating in this repository.
|
|
4
|
+
It documents the current build/test workflow and code conventions inferred from the codebase.
|
|
5
|
+
|
|
6
|
+
## Scope and Source of Truth
|
|
7
|
+
|
|
8
|
+
- Primary language: TypeScript (ESM, NodeNext).
|
|
9
|
+
- Runtime target: Node.js `>=20`.
|
|
10
|
+
- Package manager: npm (`package-lock.json` is present).
|
|
11
|
+
- Compiler config: `tsconfig.json` with `strict: true` and `rootDir: src`, `outDir: dist`.
|
|
12
|
+
- Public entry points: CLI (`src/index.ts`) and Node API (`src/api.ts`).
|
|
13
|
+
|
|
14
|
+
## Repository Rules Discovery
|
|
15
|
+
|
|
16
|
+
I checked for additional agent instruction files:
|
|
17
|
+
|
|
18
|
+
- `.cursor/rules/`: not present
|
|
19
|
+
- `.cursorrules`: not present
|
|
20
|
+
- `.github/copilot-instructions.md`: not present
|
|
21
|
+
|
|
22
|
+
So this `AGENTS.md` is the repo-local guidance file for agents.
|
|
23
|
+
|
|
24
|
+
## Build, Lint, and Test Commands
|
|
25
|
+
|
|
26
|
+
## Install
|
|
27
|
+
|
|
28
|
+
- `npm install`
|
|
29
|
+
|
|
30
|
+
## Build
|
|
31
|
+
|
|
32
|
+
- `npm run build`
|
|
33
|
+
- Runs `tsc -p tsconfig.json`
|
|
34
|
+
- Emits JS + declarations into `dist/`
|
|
35
|
+
|
|
36
|
+
## Type Check
|
|
37
|
+
|
|
38
|
+
- `npm run typecheck`
|
|
39
|
+
- Runs `tsc --noEmit -p tsconfig.json`
|
|
40
|
+
- Use this as the closest thing to linting gate today
|
|
41
|
+
|
|
42
|
+
## Lint
|
|
43
|
+
|
|
44
|
+
- There is currently **no configured lint script** (`npm run lint` does not exist).
|
|
45
|
+
- Do not assume ESLint/Prettier are configured.
|
|
46
|
+
- Keep formatting and style aligned with existing files.
|
|
47
|
+
|
|
48
|
+
## Tests
|
|
49
|
+
|
|
50
|
+
- There is currently **no test script** in this repository’s `package.json`.
|
|
51
|
+
- There is no committed `test/` directory in this repo at the moment.
|
|
52
|
+
- For now, validation is typically: build + typecheck + targeted manual CLI/API smoke checks.
|
|
53
|
+
|
|
54
|
+
## Running a single test (when test files exist)
|
|
55
|
+
|
|
56
|
+
If you add Node test files (for example `*.test.mjs`), run one test file directly with:
|
|
57
|
+
|
|
58
|
+
- `node --test path/to/file.test.mjs`
|
|
59
|
+
|
|
60
|
+
Example:
|
|
61
|
+
|
|
62
|
+
- `node --test test/recipe-smoke.test.mjs`
|
|
63
|
+
|
|
64
|
+
Notes:
|
|
65
|
+
|
|
66
|
+
- Scaffolded recipe projects generated by `clawchef scaffold` include `test/recipe-smoke.test.mjs`.
|
|
67
|
+
- Those scaffolded projects also include `npm run test:recipe` and `npm test`, but this repo currently does not.
|
|
68
|
+
|
|
69
|
+
## Coding Style Guidelines
|
|
70
|
+
|
|
71
|
+
These conventions are based on current source in `src/`.
|
|
72
|
+
|
|
73
|
+
## Formatting and syntax
|
|
74
|
+
|
|
75
|
+
- Use 2-space indentation.
|
|
76
|
+
- Use semicolons.
|
|
77
|
+
- Use double quotes for strings.
|
|
78
|
+
- Prefer trailing commas in multiline literals/calls.
|
|
79
|
+
- Keep functions focused and relatively small unless complexity requires expansion.
|
|
80
|
+
|
|
81
|
+
## Module system and imports
|
|
82
|
+
|
|
83
|
+
- Use ESM imports/exports only.
|
|
84
|
+
- For local imports, include `.js` extension in import specifiers (important for NodeNext output).
|
|
85
|
+
- Use `import type` for type-only imports.
|
|
86
|
+
- Prefer Node built-ins via `node:` prefix.
|
|
87
|
+
- Import ordering is not rigidly enforced; keep it consistent within each edited file.
|
|
88
|
+
|
|
89
|
+
## Types and strictness
|
|
90
|
+
|
|
91
|
+
- Preserve strict TypeScript compatibility (`strict: true`).
|
|
92
|
+
- Avoid `any`; use `unknown` when needed and narrow safely.
|
|
93
|
+
- Prefer explicit interfaces/types for structured data (see `src/types.ts`).
|
|
94
|
+
- Prefer narrow unions for finite modes/options (`RunScope`, provider names, etc.).
|
|
95
|
+
- Keep runtime validation in sync with static types.
|
|
96
|
+
- If a recipe shape changes, update both `src/types.ts` and `src/schema.ts`.
|
|
97
|
+
|
|
98
|
+
## Naming conventions
|
|
99
|
+
|
|
100
|
+
- `camelCase` for variables/functions.
|
|
101
|
+
- `PascalCase` for classes, interfaces, and type aliases intended as entities.
|
|
102
|
+
- `UPPER_SNAKE_CASE` for top-level constants with fixed meaning.
|
|
103
|
+
- Keep naming domain-oriented (`workspace`, `channel`, `agent`, `bootstrap`, `scope`).
|
|
104
|
+
|
|
105
|
+
## Error handling conventions
|
|
106
|
+
|
|
107
|
+
- Throw `ClawChefError` for expected/user-facing failures.
|
|
108
|
+
- Include actionable, specific messages.
|
|
109
|
+
- For caught unknown errors, convert to readable message:
|
|
110
|
+
- `err instanceof Error ? err.message : String(err)`
|
|
111
|
+
- At CLI top-level, only `ClawChefError` should map to normal error output; unknown errors become fatal.
|
|
112
|
+
|
|
113
|
+
## Logging and output
|
|
114
|
+
|
|
115
|
+
- Use `Logger` (`info`, `warn`, `debug`) for orchestration flow messages.
|
|
116
|
+
- `debug` logs should remain behind `--verbose`.
|
|
117
|
+
- Keep logs concise and operationally useful.
|
|
118
|
+
- Use `process.stdout.write` / `process.stderr.write` for CLI I/O (consistent with existing code).
|
|
119
|
+
|
|
120
|
+
## Async and filesystem patterns
|
|
121
|
+
|
|
122
|
+
- Use async/await with `node:fs/promises` APIs.
|
|
123
|
+
- Prefer non-blocking I/O, except tiny startup reads where sync is already established (`readPackageVersion`).
|
|
124
|
+
- Wrap external command execution and network operations with clear errors.
|
|
125
|
+
- Preserve cleanup behavior for temp dirs/files via `try/finally`.
|
|
126
|
+
|
|
127
|
+
## Recipe/schema evolution rules
|
|
128
|
+
|
|
129
|
+
- Schema is strict (`zod` `.strict()` used heavily); unknown keys are rejected.
|
|
130
|
+
- Enforce semantic constraints in `semanticValidate` (`src/recipe.ts`) after schema parse.
|
|
131
|
+
- Keep secret-handling guardrails intact:
|
|
132
|
+
- Inline secrets should be rejected where required.
|
|
133
|
+
- Template placeholders (`${var}`) are expected for secrets.
|
|
134
|
+
|
|
135
|
+
## CLI/API change guidelines
|
|
136
|
+
|
|
137
|
+
- Keep CLI flags in `src/cli.ts` aligned with Node API options in `src/api.ts` when feature parity is intended.
|
|
138
|
+
- Validate mutually dependent options early (for example, scope + workspace pairing).
|
|
139
|
+
- Maintain provider parity (`command`, `remote`, `mock`) when adding operations.
|
|
140
|
+
- If provider interface changes, update all provider implementations and factory wiring.
|
|
141
|
+
|
|
142
|
+
## Suggested pre-commit checklist for agents
|
|
143
|
+
|
|
144
|
+
- `npm run typecheck`
|
|
145
|
+
- `npm run build`
|
|
146
|
+
- If tests were added: run relevant Node tests, at least one targeted file
|
|
147
|
+
- Verify README snippets if CLI flags/behavior changed
|
|
148
|
+
- Confirm `src/types.ts` and `src/schema.ts` remain synchronized
|
|
149
|
+
|
|
150
|
+
## Quick architecture map
|
|
151
|
+
|
|
152
|
+
- `src/cli.ts`: command parsing + CLI runtime wiring
|
|
153
|
+
- `src/api.ts`: Node API (`cook`, `validate`, `scaffold`)
|
|
154
|
+
- `src/recipe.ts`: recipe loading, template resolution, semantic validation
|
|
155
|
+
- `src/orchestrator.ts`: execution flow across workspaces/agents/channels/files
|
|
156
|
+
- `src/openclaw/*.ts`: provider abstraction and implementations
|
|
157
|
+
- `src/schema.ts` + `src/types.ts`: validation schema and TS domain model
|
|
158
|
+
|
|
159
|
+
When in doubt, follow existing patterns in adjacent files and keep changes minimal, explicit, and type-safe.
|
package/README.md
CHANGED
|
@@ -15,10 +15,11 @@ Recipe-driven OpenClaw environment orchestrator.
|
|
|
15
15
|
- Supports scoped execution via `--scope full|files|workspace`.
|
|
16
16
|
- `full` scope runs factory reset first (with confirmation prompt unless `-s/--silent` is used).
|
|
17
17
|
- If `openclaw` is missing, auto-installs the recipe version and skips factory reset.
|
|
18
|
-
- Starts OpenClaw gateway
|
|
18
|
+
- Starts OpenClaw gateway after each recipe execution based on `--gateway-mode`.
|
|
19
19
|
- Creates workspaces and agents (default workspace path: `~/.openclaw/workspace-<workspace-name>`).
|
|
20
20
|
- Supports workspace-level assets copy via `workspaces[].assets`.
|
|
21
21
|
- Materializes files into target workspaces.
|
|
22
|
+
- Supports OpenClaw root-level assets/files via `openclaw.root` (default root path: `~/.openclaw`).
|
|
22
23
|
- Installs skills.
|
|
23
24
|
- Supports plugin preinstall via `openclaw.plugins[]` and runtime `--plugin` flags.
|
|
24
25
|
- Configures channels with `openclaw channels add`.
|
|
@@ -90,6 +91,14 @@ Skip reset confirmation prompt:
|
|
|
90
91
|
clawchef cook recipes/sample.yaml -s
|
|
91
92
|
```
|
|
92
93
|
|
|
94
|
+
Control gateway startup mode:
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
clawchef cook recipes/sample.yaml --gateway-mode service
|
|
98
|
+
clawchef cook recipes/sample.yaml --gateway-mode run
|
|
99
|
+
clawchef cook recipes/sample.yaml --gateway-mode none
|
|
100
|
+
```
|
|
101
|
+
|
|
93
102
|
Warning: `-s/--silent` suppresses the factory-reset confirmation and auto-chooses force reinstall on version mismatch.
|
|
94
103
|
Use it only in CI/non-interactive flows where destructive reset behavior is expected.
|
|
95
104
|
|
|
@@ -204,6 +213,7 @@ await scaffold("./my-recipe-project", {
|
|
|
204
213
|
- `plugins`: plugin npm specs to preinstall for this run (`string[]`)
|
|
205
214
|
- `scope`: `full | files | workspace` (default: `full`)
|
|
206
215
|
- `workspaceName`: required when `scope: "workspace"`
|
|
216
|
+
- `gatewayMode`: `service | run | none` (default: `service`)
|
|
207
217
|
- `provider`: `command | remote | mock`
|
|
208
218
|
- `remote`: remote provider config (same fields as CLI remote flags)
|
|
209
219
|
- `envFile`: custom env file path/URL; when set, default cwd `.env` loading is skipped
|
|
@@ -304,9 +314,11 @@ Supported operation values sent by clawchef:
|
|
|
304
314
|
- `ensure_version`, `factory_reset`, `start_gateway`
|
|
305
315
|
- `install_plugin`
|
|
306
316
|
- `create_workspace`, `create_agent`, `materialize_file`, `install_skill`
|
|
307
|
-
- `configure_channel`, `login_channel`
|
|
317
|
+
- `configure_channel`, `bind_channel_agent`, `login_channel`
|
|
308
318
|
- `run_agent`
|
|
309
319
|
|
|
320
|
+
For `start_gateway`, clawchef sends `{ mode: "service" | "run" }` in payload when gateway mode is enabled.
|
|
321
|
+
|
|
310
322
|
For `run_agent`, clawchef expects `output` in response for assertions.
|
|
311
323
|
|
|
312
324
|
`command` provider now defaults to the current OpenClaw CLI shape (`openclaw 2026.x`), including:
|
|
@@ -352,6 +364,8 @@ For `command` provider, default command templates are:
|
|
|
352
364
|
- `install_plugin`: `${bin} plugins install ${plugin_spec_q}`
|
|
353
365
|
- `factory_reset`: `${bin} reset --scope full --yes --non-interactive`
|
|
354
366
|
- `start_gateway`: `${bin} gateway start`
|
|
367
|
+
- `run_gateway`: `${bin} gateway run`
|
|
368
|
+
- `bind_channel_agent`: built-in `openclaw config get/set bindings` upsert (override with `openclaw.commands.bind_channel_agent`)
|
|
355
369
|
- `login_channel`: `${bin} channels login --channel ${channel_q}${account_arg}`
|
|
356
370
|
- `create_workspace`: generated from `openclaw.bootstrap` (override with `openclaw.commands.create_workspace`)
|
|
357
371
|
- `create_agent`: `${bin} agents add ${agent} --workspace ${workspace_path} --model ${model} --non-interactive --json`
|
|
@@ -377,6 +391,12 @@ channels:
|
|
|
377
391
|
- channel: "telegram"
|
|
378
392
|
token: "${telegram_bot_token}"
|
|
379
393
|
account: "default"
|
|
394
|
+
agent: "main"
|
|
395
|
+
|
|
396
|
+
- channel: "telegram"
|
|
397
|
+
token: "${alerts_bot_token}"
|
|
398
|
+
account: "alerts"
|
|
399
|
+
agent: "alerts"
|
|
380
400
|
|
|
381
401
|
- channel: "slack"
|
|
382
402
|
bot_token: "${slack_bot_token}"
|
|
@@ -392,9 +412,12 @@ channels:
|
|
|
392
412
|
Supported common fields:
|
|
393
413
|
|
|
394
414
|
- required: `channel`
|
|
395
|
-
- optional: `account`, `name`, `token`, `token_file`, `use_env`, `bot_token`, `access_token`, `app_token`, `webhook_url`, `webhook_path`, `signal_number`, `password`, `login`, `login_mode`, `login_account`
|
|
415
|
+
- optional: `account`, `agent`, `name`, `token`, `token_file`, `use_env`, `bot_token`, `access_token`, `app_token`, `webhook_url`, `webhook_path`, `signal_number`, `password`, `login`, `login_mode`, `login_account`
|
|
396
416
|
- advanced passthrough: `extra_flags` (`snake_case` keys become `--kebab-case` CLI flags)
|
|
397
417
|
|
|
418
|
+
`channels[].agent` currently supports `channel: "telegram"` only.
|
|
419
|
+
If `agent` is set and `account` is omitted, clawchef defaults `account` to the same value as `agent`.
|
|
420
|
+
|
|
398
421
|
## Workspace path behavior
|
|
399
422
|
|
|
400
423
|
- `workspaces[].path` is optional.
|
|
@@ -416,9 +439,31 @@ workspaces:
|
|
|
416
439
|
assets: "./meetingbot-assets"
|
|
417
440
|
```
|
|
418
441
|
|
|
442
|
+
## OpenClaw root files
|
|
443
|
+
|
|
444
|
+
- `openclaw.root.path` is optional.
|
|
445
|
+
- If omitted, clawchef uses `~/.openclaw`.
|
|
446
|
+
- `openclaw.root.assets` is optional and recursively copied into the root directory.
|
|
447
|
+
- `openclaw.root.files[]` runs after assets copy, so explicit file entries can override copied assets.
|
|
448
|
+
- `openclaw.root.assets` is resolved relative to the recipe file path (unless absolute path is given).
|
|
449
|
+
- Direct URL recipes do not support `openclaw.root.assets` (assets must resolve to a local directory).
|
|
450
|
+
- `openclaw.root` currently supports `command` and `mock` providers; `remote` provider is not supported.
|
|
451
|
+
|
|
452
|
+
Example:
|
|
453
|
+
|
|
454
|
+
```yaml
|
|
455
|
+
openclaw:
|
|
456
|
+
version: "2026.2.9"
|
|
457
|
+
root:
|
|
458
|
+
assets: "./openclaw-root-assets"
|
|
459
|
+
files:
|
|
460
|
+
- path: "AGENTS.md"
|
|
461
|
+
content_from: "./snippets/agents-template.md"
|
|
462
|
+
```
|
|
463
|
+
|
|
419
464
|
## File content references
|
|
420
465
|
|
|
421
|
-
In `workspaces[].files[]`, set exactly one of:
|
|
466
|
+
In `workspaces[].files[]` and `openclaw.root.files[]`, set exactly one of:
|
|
422
467
|
|
|
423
468
|
- `content`: inline text in recipe
|
|
424
469
|
- `content_from`: load text from another file/URL (loaded content supports `${var}` template rendering)
|
package/dist/api.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { OpenClawProvider, OpenClawRemoteConfig, RunScope } from "./types.js";
|
|
1
|
+
import type { GatewayMode, OpenClawProvider, OpenClawRemoteConfig, RunScope } from "./types.js";
|
|
2
2
|
import type { ScaffoldOptions, ScaffoldResult } from "./scaffold.js";
|
|
3
3
|
export interface CookOptions {
|
|
4
4
|
vars?: Record<string, string>;
|
|
@@ -9,6 +9,7 @@ export interface CookOptions {
|
|
|
9
9
|
silent?: boolean;
|
|
10
10
|
scope?: RunScope;
|
|
11
11
|
workspaceName?: string;
|
|
12
|
+
gatewayMode?: GatewayMode;
|
|
12
13
|
provider?: OpenClawProvider;
|
|
13
14
|
remote?: Partial<OpenClawRemoteConfig>;
|
|
14
15
|
envFile?: string;
|
package/dist/api.js
CHANGED
package/dist/cli.js
CHANGED
|
@@ -61,6 +61,12 @@ function parseScope(value) {
|
|
|
61
61
|
}
|
|
62
62
|
throw new ClawChefError(`Invalid --scope value: ${value}. Expected full, files, or workspace`);
|
|
63
63
|
}
|
|
64
|
+
function parseGatewayMode(value) {
|
|
65
|
+
if (value === "service" || value === "run" || value === "none") {
|
|
66
|
+
return value;
|
|
67
|
+
}
|
|
68
|
+
throw new ClawChefError(`Invalid --gateway-mode value: ${value}. Expected service, run, or none`);
|
|
69
|
+
}
|
|
64
70
|
function parseOptionalInt(value, fieldName) {
|
|
65
71
|
if (value === undefined) {
|
|
66
72
|
return undefined;
|
|
@@ -101,6 +107,7 @@ export function buildCli() {
|
|
|
101
107
|
.option("-s, --silent", "Skip reset confirmation prompt", false)
|
|
102
108
|
.option("--scope <scope>", "Run scope: full | files | workspace", "full")
|
|
103
109
|
.option("--workspace <name>", "Workspace name (required when --scope workspace)")
|
|
110
|
+
.option("--gateway-mode <mode>", "Gateway mode: service | run | none", "service")
|
|
104
111
|
.option("--dotenv-ref <path-or-url>", "Load env vars from local file or HTTP URL")
|
|
105
112
|
.option("--provider <provider>", "Execution provider: command | remote | mock")
|
|
106
113
|
.option("--plugin <npm-spec>", "Preinstall plugin package (repeatable)", (v, p) => p.concat([v]), [])
|
|
@@ -119,6 +126,7 @@ export function buildCli() {
|
|
|
119
126
|
}
|
|
120
127
|
const provider = parseProvider(opts.provider ?? readEnv("CLAWCHEF_PROVIDER") ?? "command");
|
|
121
128
|
const scope = parseScope(String(opts.scope ?? "full"));
|
|
129
|
+
const gatewayMode = parseGatewayMode(String(opts.gatewayMode ?? "service"));
|
|
122
130
|
const workspaceName = opts.workspace?.trim() ? String(opts.workspace).trim() : undefined;
|
|
123
131
|
if (scope === "workspace" && !workspaceName) {
|
|
124
132
|
throw new ClawChefError("--scope workspace requires --workspace <name>");
|
|
@@ -131,6 +139,7 @@ export function buildCli() {
|
|
|
131
139
|
plugins: parsePluginFlags(opts.plugin),
|
|
132
140
|
scope,
|
|
133
141
|
workspaceName,
|
|
142
|
+
gatewayMode,
|
|
134
143
|
dryRun: Boolean(opts.dryRun),
|
|
135
144
|
allowMissing: Boolean(opts.allowMissing),
|
|
136
145
|
verbose: Boolean(opts.verbose),
|
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
import type { AgentDef, ChannelDef, ConversationDef, OpenClawSection } from "../types.js";
|
|
1
|
+
import type { AgentDef, ChannelDef, ConversationDef, GatewayMode, OpenClawSection } from "../types.js";
|
|
2
2
|
import type { EnsureVersionResult, OpenClawProvider, ResolvedWorkspaceDef } from "./provider.js";
|
|
3
3
|
export declare class CommandOpenClawProvider implements OpenClawProvider {
|
|
4
4
|
private readonly stagedMessages;
|
|
5
5
|
ensureVersion(config: OpenClawSection, dryRun: boolean, silent: boolean, preserveExistingState: boolean): Promise<EnsureVersionResult>;
|
|
6
6
|
factoryReset(config: OpenClawSection, dryRun: boolean): Promise<void>;
|
|
7
7
|
installPlugin(config: OpenClawSection, pluginSpec: string, dryRun: boolean): Promise<void>;
|
|
8
|
-
startGateway(config: OpenClawSection, dryRun: boolean): Promise<void>;
|
|
8
|
+
startGateway(config: OpenClawSection, mode: GatewayMode, dryRun: boolean): Promise<void>;
|
|
9
9
|
createWorkspace(config: OpenClawSection, workspace: ResolvedWorkspaceDef, dryRun: boolean): Promise<void>;
|
|
10
10
|
configureChannel(config: OpenClawSection, channel: ChannelDef, dryRun: boolean): Promise<void>;
|
|
11
|
+
bindChannelAgent(config: OpenClawSection, channel: ChannelDef, agent: string, dryRun: boolean): Promise<void>;
|
|
11
12
|
loginChannel(config: OpenClawSection, channel: ChannelDef, dryRun: boolean): Promise<void>;
|
|
12
13
|
createAgent(config: OpenClawSection, agent: AgentDef, workspacePath: string, dryRun: boolean): Promise<void>;
|
|
13
14
|
installSkill(config: OpenClawSection, workspace: string, agent: string, skill: string, dryRun: boolean): Promise<void>;
|
|
@@ -13,7 +13,9 @@ const DEFAULT_COMMANDS = {
|
|
|
13
13
|
install_plugin: "${bin} plugins install ${plugin_spec_q}",
|
|
14
14
|
factory_reset: "${bin} reset --scope full --yes --non-interactive",
|
|
15
15
|
start_gateway: "${bin} gateway start",
|
|
16
|
+
run_gateway: "${bin} gateway run",
|
|
16
17
|
enable_plugin: "",
|
|
18
|
+
bind_channel_agent: "",
|
|
17
19
|
login_channel: "${bin} channels login --channel ${channel_q}${account_arg}",
|
|
18
20
|
create_agent: "${bin} agents add ${agent} --workspace ${workspace_path} --model ${model} --non-interactive --json",
|
|
19
21
|
install_skill: "${bin} skills check",
|
|
@@ -227,6 +229,36 @@ function bootstrapRuntimeEnv(bootstrap) {
|
|
|
227
229
|
}
|
|
228
230
|
return env;
|
|
229
231
|
}
|
|
232
|
+
function isAccountLevelBinding(item, channel, account) {
|
|
233
|
+
const match = item.match;
|
|
234
|
+
if (!match || typeof match !== "object") {
|
|
235
|
+
return false;
|
|
236
|
+
}
|
|
237
|
+
if (match.channel !== channel || match.accountId !== account) {
|
|
238
|
+
return false;
|
|
239
|
+
}
|
|
240
|
+
return (match.peer === undefined
|
|
241
|
+
&& match.parentPeer === undefined
|
|
242
|
+
&& match.guildId === undefined
|
|
243
|
+
&& match.teamId === undefined
|
|
244
|
+
&& match.roles === undefined);
|
|
245
|
+
}
|
|
246
|
+
function parseBindingsJson(raw) {
|
|
247
|
+
if (!raw.trim()) {
|
|
248
|
+
return [];
|
|
249
|
+
}
|
|
250
|
+
try {
|
|
251
|
+
const parsed = JSON.parse(raw);
|
|
252
|
+
if (!Array.isArray(parsed)) {
|
|
253
|
+
throw new ClawChefError("openclaw config bindings is not an array");
|
|
254
|
+
}
|
|
255
|
+
return parsed;
|
|
256
|
+
}
|
|
257
|
+
catch (err) {
|
|
258
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
259
|
+
throw new ClawChefError(`Failed to parse openclaw bindings JSON: ${message}`);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
230
262
|
export class CommandOpenClawProvider {
|
|
231
263
|
stagedMessages = new Map();
|
|
232
264
|
async ensureVersion(config, dryRun, silent, preserveExistingState) {
|
|
@@ -331,9 +363,13 @@ export class CommandOpenClawProvider {
|
|
|
331
363
|
}
|
|
332
364
|
await runShell(cmd, dryRun);
|
|
333
365
|
}
|
|
334
|
-
async startGateway(config, dryRun) {
|
|
366
|
+
async startGateway(config, mode, dryRun) {
|
|
367
|
+
if (mode === "none") {
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
335
370
|
const bin = config.bin ?? "openclaw";
|
|
336
|
-
const
|
|
371
|
+
const key = mode === "run" ? "run_gateway" : "start_gateway";
|
|
372
|
+
const startCmd = commandFor(config, key, { bin, version: config.version });
|
|
337
373
|
if (!startCmd.trim()) {
|
|
338
374
|
return;
|
|
339
375
|
}
|
|
@@ -408,6 +444,53 @@ export class CommandOpenClawProvider {
|
|
|
408
444
|
const cmd = `${bin} channels add ${flags.join(" ")}`;
|
|
409
445
|
await runShell(cmd, dryRun);
|
|
410
446
|
}
|
|
447
|
+
async bindChannelAgent(config, channel, agent, dryRun) {
|
|
448
|
+
const account = channel.account?.trim();
|
|
449
|
+
if (!account) {
|
|
450
|
+
throw new ClawChefError(`Channel ${channel.channel} requires account for agent binding`);
|
|
451
|
+
}
|
|
452
|
+
const bin = config.bin ?? "openclaw";
|
|
453
|
+
const customTemplate = config.commands?.bind_channel_agent;
|
|
454
|
+
if (customTemplate?.trim()) {
|
|
455
|
+
const customCmd = fillTemplate(customTemplate, {
|
|
456
|
+
bin,
|
|
457
|
+
version: config.version,
|
|
458
|
+
channel: channel.channel,
|
|
459
|
+
channel_q: shellQuote(channel.channel),
|
|
460
|
+
account,
|
|
461
|
+
account_q: shellQuote(account),
|
|
462
|
+
agent,
|
|
463
|
+
agent_q: shellQuote(agent),
|
|
464
|
+
});
|
|
465
|
+
if (customCmd.trim()) {
|
|
466
|
+
await runShell(customCmd, dryRun);
|
|
467
|
+
}
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
if (dryRun) {
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
const getCmd = `${bin} config get bindings --json 2>/dev/null || printf '[]'`;
|
|
474
|
+
const rawBindings = await runShell(getCmd, false);
|
|
475
|
+
const bindings = parseBindingsJson(rawBindings);
|
|
476
|
+
const nextBinding = {
|
|
477
|
+
agentId: agent,
|
|
478
|
+
match: {
|
|
479
|
+
channel: channel.channel,
|
|
480
|
+
accountId: account,
|
|
481
|
+
},
|
|
482
|
+
};
|
|
483
|
+
const index = bindings.findIndex((item) => isAccountLevelBinding(item, channel.channel, account));
|
|
484
|
+
if (index >= 0) {
|
|
485
|
+
bindings[index] = nextBinding;
|
|
486
|
+
}
|
|
487
|
+
else {
|
|
488
|
+
bindings.push(nextBinding);
|
|
489
|
+
}
|
|
490
|
+
const json = JSON.stringify(bindings);
|
|
491
|
+
const setCmd = `${bin} config set bindings ${shellQuote(json)} --json`;
|
|
492
|
+
await runShell(setCmd, false);
|
|
493
|
+
}
|
|
411
494
|
async loginChannel(config, channel, dryRun) {
|
|
412
495
|
if (!channel.login) {
|
|
413
496
|
return;
|
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
import type { AgentDef, ChannelDef, ConversationDef, OpenClawSection } from "../types.js";
|
|
1
|
+
import type { AgentDef, ChannelDef, ConversationDef, GatewayMode, OpenClawSection } from "../types.js";
|
|
2
2
|
import type { EnsureVersionResult, OpenClawProvider, ResolvedWorkspaceDef } from "./provider.js";
|
|
3
3
|
export declare class MockOpenClawProvider implements OpenClawProvider {
|
|
4
4
|
private state;
|
|
5
5
|
ensureVersion(config: OpenClawSection, _dryRun: boolean, _silent: boolean, _preserveExistingState: boolean): Promise<EnsureVersionResult>;
|
|
6
6
|
installPlugin(_config: OpenClawSection, _pluginSpec: string, _dryRun: boolean): Promise<void>;
|
|
7
7
|
factoryReset(_config: OpenClawSection, _dryRun: boolean): Promise<void>;
|
|
8
|
-
startGateway(_config: OpenClawSection, _dryRun: boolean): Promise<void>;
|
|
8
|
+
startGateway(_config: OpenClawSection, _mode: GatewayMode, _dryRun: boolean): Promise<void>;
|
|
9
9
|
createWorkspace(_config: OpenClawSection, workspace: ResolvedWorkspaceDef, _dryRun: boolean): Promise<void>;
|
|
10
10
|
configureChannel(_config: OpenClawSection, channel: ChannelDef, _dryRun: boolean): Promise<void>;
|
|
11
|
+
bindChannelAgent(_config: OpenClawSection, _channel: ChannelDef, _agent: string, _dryRun: boolean): Promise<void>;
|
|
11
12
|
loginChannel(_config: OpenClawSection, _channel: ChannelDef, _dryRun: boolean): Promise<void>;
|
|
12
13
|
createAgent(_config: OpenClawSection, agent: AgentDef, _workspacePath: string, _dryRun: boolean): Promise<void>;
|
|
13
14
|
installSkill(_config: OpenClawSection, workspace: string, agent: string, skill: string, _dryRun: boolean): Promise<void>;
|
|
@@ -35,7 +35,7 @@ export class MockOpenClawProvider {
|
|
|
35
35
|
this.state.skills.clear();
|
|
36
36
|
this.state.messages.clear();
|
|
37
37
|
}
|
|
38
|
-
async startGateway(_config, _dryRun) {
|
|
38
|
+
async startGateway(_config, _mode, _dryRun) {
|
|
39
39
|
return;
|
|
40
40
|
}
|
|
41
41
|
async createWorkspace(_config, workspace, _dryRun) {
|
|
@@ -44,6 +44,9 @@ export class MockOpenClawProvider {
|
|
|
44
44
|
async configureChannel(_config, channel, _dryRun) {
|
|
45
45
|
this.state.channels.add(`${channel.channel}::${channel.account ?? "default"}`);
|
|
46
46
|
}
|
|
47
|
+
async bindChannelAgent(_config, _channel, _agent, _dryRun) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
47
50
|
async loginChannel(_config, _channel, _dryRun) {
|
|
48
51
|
return;
|
|
49
52
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AgentDef, ChannelDef, ConversationDef, OpenClawSection, WorkspaceDef } from "../types.js";
|
|
1
|
+
import type { AgentDef, ChannelDef, ConversationDef, GatewayMode, OpenClawSection, WorkspaceDef } from "../types.js";
|
|
2
2
|
export type ResolvedWorkspaceDef = WorkspaceDef & {
|
|
3
3
|
path: string;
|
|
4
4
|
};
|
|
@@ -9,9 +9,10 @@ export interface OpenClawProvider {
|
|
|
9
9
|
ensureVersion(config: OpenClawSection, dryRun: boolean, silent: boolean, preserveExistingState: boolean): Promise<EnsureVersionResult>;
|
|
10
10
|
installPlugin(config: OpenClawSection, pluginSpec: string, dryRun: boolean): Promise<void>;
|
|
11
11
|
factoryReset(config: OpenClawSection, dryRun: boolean): Promise<void>;
|
|
12
|
-
startGateway(config: OpenClawSection, dryRun: boolean): Promise<void>;
|
|
12
|
+
startGateway(config: OpenClawSection, mode: GatewayMode, dryRun: boolean): Promise<void>;
|
|
13
13
|
createWorkspace(config: OpenClawSection, workspace: ResolvedWorkspaceDef, dryRun: boolean): Promise<void>;
|
|
14
14
|
configureChannel(config: OpenClawSection, channel: ChannelDef, dryRun: boolean): Promise<void>;
|
|
15
|
+
bindChannelAgent(config: OpenClawSection, channel: ChannelDef, agent: string, dryRun: boolean): Promise<void>;
|
|
15
16
|
loginChannel(config: OpenClawSection, channel: ChannelDef, dryRun: boolean): Promise<void>;
|
|
16
17
|
materializeFile?(config: OpenClawSection, workspace: string, filePath: string, content: string, overwrite: boolean | undefined, dryRun: boolean): Promise<void>;
|
|
17
18
|
createAgent(config: OpenClawSection, agent: AgentDef, workspacePath: string, dryRun: boolean): Promise<void>;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AgentDef, ChannelDef, ConversationDef, OpenClawRemoteConfig, OpenClawSection } from "../types.js";
|
|
1
|
+
import type { AgentDef, ChannelDef, ConversationDef, GatewayMode, OpenClawRemoteConfig, OpenClawSection } from "../types.js";
|
|
2
2
|
import type { EnsureVersionResult, OpenClawProvider, ResolvedWorkspaceDef } from "./provider.js";
|
|
3
3
|
export declare class RemoteOpenClawProvider implements OpenClawProvider {
|
|
4
4
|
private readonly stagedMessages;
|
|
@@ -8,9 +8,10 @@ export declare class RemoteOpenClawProvider implements OpenClawProvider {
|
|
|
8
8
|
ensureVersion(config: OpenClawSection, dryRun: boolean, _silent: boolean, _preserveExistingState: boolean): Promise<EnsureVersionResult>;
|
|
9
9
|
installPlugin(config: OpenClawSection, pluginSpec: string, dryRun: boolean): Promise<void>;
|
|
10
10
|
factoryReset(config: OpenClawSection, dryRun: boolean): Promise<void>;
|
|
11
|
-
startGateway(config: OpenClawSection, dryRun: boolean): Promise<void>;
|
|
11
|
+
startGateway(config: OpenClawSection, mode: GatewayMode, dryRun: boolean): Promise<void>;
|
|
12
12
|
createWorkspace(config: OpenClawSection, workspace: ResolvedWorkspaceDef, dryRun: boolean): Promise<void>;
|
|
13
13
|
configureChannel(config: OpenClawSection, channel: ChannelDef, dryRun: boolean): Promise<void>;
|
|
14
|
+
bindChannelAgent(config: OpenClawSection, channel: ChannelDef, agent: string, dryRun: boolean): Promise<void>;
|
|
14
15
|
loginChannel(config: OpenClawSection, channel: ChannelDef, dryRun: boolean): Promise<void>;
|
|
15
16
|
materializeFile(config: OpenClawSection, workspace: string, filePath: string, content: string, overwrite: boolean | undefined, dryRun: boolean): Promise<void>;
|
|
16
17
|
createAgent(config: OpenClawSection, agent: AgentDef, workspacePath: string, dryRun: boolean): Promise<void>;
|
|
@@ -110,8 +110,11 @@ export class RemoteOpenClawProvider {
|
|
|
110
110
|
async factoryReset(config, dryRun) {
|
|
111
111
|
await this.perform(config, "factory_reset", undefined, dryRun);
|
|
112
112
|
}
|
|
113
|
-
async startGateway(config, dryRun) {
|
|
114
|
-
|
|
113
|
+
async startGateway(config, mode, dryRun) {
|
|
114
|
+
if (mode === "none") {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
await this.perform(config, "start_gateway", { mode }, dryRun);
|
|
115
118
|
}
|
|
116
119
|
async createWorkspace(config, workspace, dryRun) {
|
|
117
120
|
await this.perform(config, "create_workspace", { workspace }, dryRun);
|
|
@@ -119,6 +122,13 @@ export class RemoteOpenClawProvider {
|
|
|
119
122
|
async configureChannel(config, channel, dryRun) {
|
|
120
123
|
await this.perform(config, "configure_channel", { channel }, dryRun);
|
|
121
124
|
}
|
|
125
|
+
async bindChannelAgent(config, channel, agent, dryRun) {
|
|
126
|
+
await this.perform(config, "bind_channel_agent", {
|
|
127
|
+
channel: channel.channel,
|
|
128
|
+
account: channel.account,
|
|
129
|
+
agent,
|
|
130
|
+
}, dryRun);
|
|
131
|
+
}
|
|
122
132
|
async loginChannel(config, channel, dryRun) {
|
|
123
133
|
await this.perform(config, "login_channel", { channel }, dryRun);
|
|
124
134
|
}
|