direxio-deployer 0.1.1 → 0.1.3
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 +1 -1
- package/README.md +10 -2
- package/README_zh.md +10 -2
- package/SKILL.md +39 -11
- package/package.json +2 -2
- package/references/agent-targets.md +7 -1
- package/references/deployment-workflow.md +11 -4
- package/references/runtime-wiring.md +7 -7
- package/references/state-machine.md +2 -2
- package/references/token-refresh.md +1 -1
- package/references/windows-deployment-notes.md +1 -1
- package/scripts/cloud-init/init-tokens.sh +10 -5
- package/scripts/lib/state.sh +22 -0
- package/scripts/mcp-tools-list.mjs +59 -15
- package/scripts/orchestrate.sh +42 -3
- package/scripts/phases/s3_provision.sh +1 -1
- package/scripts/phases/s6_wire_local.sh +56 -24
package/AGENTS.md
CHANGED
|
@@ -42,7 +42,7 @@ If a change writes a path into `state.json`, `credentials.json`, `env`, `cc-conn
|
|
|
42
42
|
## Direxio Connect Wiring
|
|
43
43
|
|
|
44
44
|
- S5/S6 must fail closed when `agent_room_id` is missing or uses a legacy pseudo id such as `!agent:<domain>`.
|
|
45
|
-
- S6 must create a Matrix session through `agent.matrix_session.create` and require `@agent:<server>` for the bridge. Returning `@owner:<server>` is a server-side compatibility failure.
|
|
45
|
+
- S6 must create a Matrix session through `agent.matrix_session.create` using `agent_token`, not owner `access_token`, and require `@agent:<server>` for the bridge. Returning `@owner:<server>` is a server-side compatibility failure.
|
|
46
46
|
- The generated cc-connect config must contain one Matrix platform and must restrict sync/replies to the real `agent_room_id`.
|
|
47
47
|
- The generated agent config must preserve the selected connect agent type and optional agent-specific TOML. Some providers require more than `cmd`; for example `reasonix` needs `serve_url`, `tmux` needs `session`, and generic `acp` may need command/args.
|
|
48
48
|
- `DIREXIO_AGENT_INSTALL=auto` may install/start `direxio-connect`; `recommend` must only write files and print commands.
|
package/README.md
CHANGED
|
@@ -23,6 +23,14 @@ Install the deployer skill from npm, then place it into the current agent runtim
|
|
|
23
23
|
|
|
24
24
|
The GitHub repository keeps tests for maintainers and CI, but the published npm package and installed skill copy exclude `tests/` to keep user installs small.
|
|
25
25
|
|
|
26
|
+
If you want Codex to install and deploy in one instruction, do not say "install skills <GitHub URL>". That triggers GitHub skill installation and can place the skill in the global `~/.codex` directory. Use a short instruction that tells the agent to read this README's npm/project-local install rule:
|
|
27
|
+
|
|
28
|
+
```text
|
|
29
|
+
Follow the direxio-deployer README npm/project-local install rule, then deploy Direxio with domain __DOMAIN__.
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
After reading this instruction, the agent should run the npm install commands below; it should not switch to a GitHub skill installer.
|
|
33
|
+
|
|
26
34
|
POSIX shells:
|
|
27
35
|
|
|
28
36
|
```bash
|
|
@@ -53,7 +61,7 @@ direxio-deployer skill install --agent codex --scope global
|
|
|
53
61
|
The installer writes `.direxio-skill-install.json` into the target directory and refuses to overwrite unmanaged existing content unless `--force` is provided. To pin a version, install that package version first:
|
|
54
62
|
|
|
55
63
|
```bash
|
|
56
|
-
npm install -g direxio-deployer@0.1.
|
|
64
|
+
npm install -g direxio-deployer@0.1.3
|
|
57
65
|
direxio-deployer skill update --agent codex --scope project --project .
|
|
58
66
|
```
|
|
59
67
|
|
|
@@ -120,7 +128,7 @@ bash scripts/orchestrate.sh
|
|
|
120
128
|
```
|
|
121
129
|
|
|
122
130
|
Supported install modes: `recommended` and `cc-connect`.
|
|
123
|
-
If `DIREXIO_AGENT_PLATFORM=auto` cannot identify a single supported runtime, set `DIREXIO_CC_CONNECT_AGENT` explicitly. For OpenClaw or Hermes defaults, force the host runtime with `DIREXIO_AGENT_PLATFORM=openclaw` or `DIREXIO_AGENT_PLATFORM=hermes`; setting only `DIREXIO_CC_CONNECT_AGENT=acp` selects generic ACP and requires manual options.
|
|
131
|
+
If `DIREXIO_AGENT_PLATFORM=auto` cannot identify a single supported runtime, set `DIREXIO_CC_CONNECT_AGENT` explicitly. For OpenClaw or Hermes defaults, force the host runtime with `DIREXIO_AGENT_PLATFORM=openclaw` or `DIREXIO_AGENT_PLATFORM=hermes`; setting only `DIREXIO_CC_CONNECT_AGENT=acp` selects generic ACP and requires manual options. OpenClaw Gateway ACP defaults to `["acp", "--session", "agent:main:main"]` and lets `openclaw acp` auto-detect the Gateway from `~/.openclaw/openclaw.json`. To force explicit Gateway settings, set all of `DIREXIO_OPENCLAW_ACP_URL`, `DIREXIO_OPENCLAW_ACP_TOKEN_FILE`, and `DIREXIO_OPENCLAW_ACP_SESSION` from the current OpenClaw runtime after pairing. Use `DIREXIO_OPENCLAW_ACP_ARGS_TOML` only when you need to provide the complete OpenClaw ACP args array yourself. Use `DIREXIO_HERMES_ACP_ARGS_TOML` for the child Hermes args; S6 prefixes the `hermes-acp-adapter -- <hermes-command>` wrapper automatically.
|
|
124
132
|
|
|
125
133
|
Check status:
|
|
126
134
|
|
package/README_zh.md
CHANGED
|
@@ -21,6 +21,14 @@
|
|
|
21
21
|
|
|
22
22
|
GitHub 仓库保留测试用于维护和 CI,但发布到 npm 的包以及安装到智能体 skill 目录的副本不包含 `tests/`,以减小用户安装体积。
|
|
23
23
|
|
|
24
|
+
如果你想让 Codex 一句话安装并开始部署,不要说“安装 skills <GitHub 链接>”。那会触发 GitHub skill 安装器,容易安装到全局 `~/.codex`。推荐只说短句,让 agent 先读本 README 中的 npm/project-local 安装规则:
|
|
25
|
+
|
|
26
|
+
```text
|
|
27
|
+
请按 direxio-deployer README 的 npm/project-local 规则安装 skill,然后部署 Direxio,域名 __DOMAIN__。
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Agent 读到这句后应执行下方 npm 安装命令;不要改用 GitHub skill installer。
|
|
31
|
+
|
|
24
32
|
POSIX shell:
|
|
25
33
|
|
|
26
34
|
```bash
|
|
@@ -51,7 +59,7 @@ direxio-deployer skill install --agent codex --scope global
|
|
|
51
59
|
安装器会在目标目录写入 `.direxio-skill-install.json`,并拒绝覆盖没有该 manifest 的既有目录,除非显式传入 `--force`。如需固定版本,先安装指定 npm 版本:
|
|
52
60
|
|
|
53
61
|
```bash
|
|
54
|
-
npm install -g direxio-deployer@0.1.
|
|
62
|
+
npm install -g direxio-deployer@0.1.3
|
|
55
63
|
direxio-deployer skill update --agent codex --scope project --project .
|
|
56
64
|
```
|
|
57
65
|
|
|
@@ -117,7 +125,7 @@ bash scripts/orchestrate.sh
|
|
|
117
125
|
```
|
|
118
126
|
|
|
119
127
|
可选安装模式:`recommended`、`cc-connect`。
|
|
120
|
-
如果 `DIREXIO_AGENT_PLATFORM=auto` 无法唯一识别当前运行时,显式设置 `DIREXIO_CC_CONNECT_AGENT`。需要触发 OpenClaw 或 Hermes 默认配置时,设置 `DIREXIO_AGENT_PLATFORM=openclaw` 或 `DIREXIO_AGENT_PLATFORM=hermes`;只设置 `DIREXIO_CC_CONNECT_AGENT=acp` 会进入通用 ACP,需要手动提供 options。OpenClaw Gateway ACP
|
|
128
|
+
如果 `DIREXIO_AGENT_PLATFORM=auto` 无法唯一识别当前运行时,显式设置 `DIREXIO_CC_CONNECT_AGENT`。需要触发 OpenClaw 或 Hermes 默认配置时,设置 `DIREXIO_AGENT_PLATFORM=openclaw` 或 `DIREXIO_AGENT_PLATFORM=hermes`;只设置 `DIREXIO_CC_CONNECT_AGENT=acp` 会进入通用 ACP,需要手动提供 options。OpenClaw Gateway ACP 默认写入 `["acp", "--session", "agent:main:main"]`,让 `openclaw acp` 从 `~/.openclaw/openclaw.json` 自动发现 Gateway。需要强制指定 Gateway 时,完成 pairing 后从当前 OpenClaw runtime 同时填写 `DIREXIO_OPENCLAW_ACP_URL`、`DIREXIO_OPENCLAW_ACP_TOKEN_FILE` 和 `DIREXIO_OPENCLAW_ACP_SESSION`。只有需要完整覆盖 OpenClaw ACP args 数组时才使用 `DIREXIO_OPENCLAW_ACP_ARGS_TOML`;Hermes 自定义参数用 `DIREXIO_HERMES_ACP_ARGS_TOML`,S6 会自动在前面加上 `hermes-acp-adapter -- <hermes-command>`。
|
|
121
129
|
|
|
122
130
|
查看状态:
|
|
123
131
|
|
package/SKILL.md
CHANGED
|
@@ -207,6 +207,18 @@ Step-by-step onboarding flow:
|
|
|
207
207
|
- Give a short billing warning before the first mutating AWS command:
|
|
208
208
|
"This will create paid AWS resources for the server. They keep billing
|
|
209
209
|
until destroyed."
|
|
210
|
+
- When AWS CLI is available after credentials are verified, make a best
|
|
211
|
+
effort Free Tier account-plan check:
|
|
212
|
+
```bash
|
|
213
|
+
aws freetier get-account-plan-state --output json
|
|
214
|
+
```
|
|
215
|
+
If it returns `accountPlanRemainingCredits`, mention the remaining credit
|
|
216
|
+
amount and expiration without treating it as a guarantee. If the command
|
|
217
|
+
is unavailable or fails, use the fixed wording: "AWS currently advertises
|
|
218
|
+
100 USD initial credits for new customer accounts, plus possible
|
|
219
|
+
additional credits after completing Free Tier activities. These credits
|
|
220
|
+
may cover a small trial deployment, but coverage is account-specific and
|
|
221
|
+
must be verified in AWS Billing Console."
|
|
210
222
|
- **Provide an upfront monthly cost estimate** based on the selected
|
|
211
223
|
region and instance type, so the user can decide whether to proceed.
|
|
212
224
|
`scripts/orchestrate.sh` records `cost_estimate` in state before the
|
|
@@ -252,10 +264,15 @@ Step-by-step onboarding flow:
|
|
|
252
264
|
- If the user asks what is billed, mention EC2/server, fixed public IPv4 or
|
|
253
265
|
Elastic IP, storage, DNS, network traffic, and call relay traffic.
|
|
254
266
|
|
|
255
|
-
Required first-time deployment confirmation
|
|
267
|
+
Required first-time deployment confirmation. Fill in the concrete domain,
|
|
268
|
+
profile, and region, and include the Free Tier sentence immediately before the
|
|
269
|
+
confirmation line:
|
|
256
270
|
|
|
257
271
|
```text
|
|
258
|
-
|
|
272
|
+
Please confirm before I deploy. AWS new customer accounts may have Free Tier credits, currently advertised as 100 USD initial credits plus possible additional credits; these credits may cover a small trial deployment, but actual coverage must be verified in AWS Billing Console.
|
|
273
|
+
|
|
274
|
+
Reply with this exact sentence:
|
|
275
|
+
I confirm that I have an active AWS account, the long-lived domain <domain>, and authorize the current <profile-or-identity> AWS profile in <region> to create the Direxio service. I understand this can create billable AWS resources, credits are not guaranteed to cover all usage, and resources keep billing until destroyed.
|
|
259
276
|
```
|
|
260
277
|
|
|
261
278
|
If any prerequisite is missing, stop deployment and guide the user through that
|
|
@@ -280,6 +297,13 @@ Lightsail is a future deployment mode, not a current automatic option. Lightsail
|
|
|
280
297
|
|
|
281
298
|
When the user asks to install or update this skill itself, or asks to wire Direxio into a local agent runtime, read `references/agent-targets.md` first. It is the source of truth for connent/connect agent targets, legacy host-runtime aliases, generic targets, and unknown targets.
|
|
282
299
|
|
|
300
|
+
If the user says "install skills" and includes the GitHub repository URL
|
|
301
|
+
`YingSuiAI/direxio-deployer`, do not use a generic GitHub skill installer for
|
|
302
|
+
normal deployment use. First install the npm package and then run
|
|
303
|
+
`direxio-deployer skill install --agent <runtime> --scope project --project
|
|
304
|
+
<project-root>`. Use a Git clone only when the user explicitly asks for
|
|
305
|
+
deployer development or local patching.
|
|
306
|
+
|
|
283
307
|
For this skill repository itself, first determine whether the current working directory belongs to a project or workspace. Treat an explicit workspace root, project files, or an existing agent-specific directory such as `.codex/`, `.claude/`, `.gemini/`, `.cursor/`, `.github/copilot/`, `.devin/`, `.opencode/`, `.qoder/`, `.pi/`, `.openclaw/`, or `.hermes/` as a project target.
|
|
284
308
|
|
|
285
309
|
If a project target exists, install or update this skill with the versioned npm CLI at the runtime-specific project-local path from `references/agent-targets.md`:
|
|
@@ -316,9 +340,9 @@ Post-deploy agent wiring is controlled by:
|
|
|
316
340
|
```bash
|
|
317
341
|
DIREXIO_AGENT_PLATFORM=auto
|
|
318
342
|
DIREXIO_CC_CONNECT_AGENT=<optional connect agent>
|
|
319
|
-
DIREXIO_OPENCLAW_ACP_URL=<
|
|
320
|
-
DIREXIO_OPENCLAW_ACP_TOKEN_FILE=<
|
|
321
|
-
DIREXIO_OPENCLAW_ACP_SESSION=<
|
|
343
|
+
DIREXIO_OPENCLAW_ACP_URL=<optional explicit OpenClaw gateway URL>
|
|
344
|
+
DIREXIO_OPENCLAW_ACP_TOKEN_FILE=<optional explicit OpenClaw gateway token file>
|
|
345
|
+
DIREXIO_OPENCLAW_ACP_SESSION=<optional OpenClaw ACP session; defaults to agent:main:main>
|
|
322
346
|
DIREXIO_AGENT_INSTALL=recommend
|
|
323
347
|
DIREXIO_AGENT_INSTALL_MODE=recommended
|
|
324
348
|
```
|
|
@@ -327,11 +351,11 @@ The only supported local conversation bridge is `direxio-connect`, installed fro
|
|
|
327
351
|
|
|
328
352
|
The local MCP tool surface is `direxio-mcp`, installed from `direxio-mcp@latest` by default. S6 writes `mcp/codex.toml`, `mcp/openclaw.md`, `mcp/openclaw-server.json`, `mcp/hermes.mcp.json`, `mcp/mcp-servers.json`, and `mcp/env`; these artifacts point to `credentials.json` by `DIREXIO_CREDENTIALS_FILE`. OpenClaw must be configured through the generated `openclaw mcp set` command in `mcp/openclaw.md`; do not paste MCP JSON into `~/.openclaw/openclaw.json`. Keep this separate from cc-connect: cc-connect must use its direct Matrix config and must not use `DIREXIO_CREDENTIALS_FILE`.
|
|
329
353
|
|
|
330
|
-
`DIREXIO_CC_CONNECT_AGENT` is the preferred explicit selector. Supported values match connent/connect: `acp`, `antigravity`, `claudecode`, `codex`, `copilot`, `cursor`, `devin`, `gemini`, `iflow`, `kimi`, `opencode`, `pi`, `qoder`, `reasonix`, and `tmux`. Detected OpenClaw and Hermes runtimes map to `cc_connect_agent=acp`; they are not native connect agent types. OpenClaw uses `cmd = "openclaw"`
|
|
354
|
+
`DIREXIO_CC_CONNECT_AGENT` is the preferred explicit selector. Supported values match connent/connect: `acp`, `antigravity`, `claudecode`, `codex`, `copilot`, `cursor`, `devin`, `gemini`, `iflow`, `kimi`, `opencode`, `pi`, `qoder`, `reasonix`, and `tmux`. Detected OpenClaw and Hermes runtimes map to `cc_connect_agent=acp`; they are not native connect agent types. OpenClaw uses `cmd = "openclaw"` with args `["acp", "--session", "agent:main:main"]` by default, letting `openclaw acp` auto-discover the Gateway from `~/.openclaw/openclaw.json`. If the operator needs to force explicit Gateway settings, S6 requires all three real values from the current OpenClaw runtime after pairing: `DIREXIO_OPENCLAW_ACP_URL`, `DIREXIO_OPENCLAW_ACP_TOKEN_FILE`, and `DIREXIO_OPENCLAW_ACP_SESSION`; do not guess these values or reuse old chat output. Hermes uses `cmd = "direxio-connect"` with `args = ["hermes-acp-adapter", "--", "hermes", "acp"]` so the Direxio compatibility layer can suppress Hermes reasoning text before it reaches the Matrix room. Use `DIREXIO_CC_CONNECT_AGENT_CMD`, `DIREXIO_<AGENT>_COMMAND`, and when needed `DIREXIO_CC_CONNECT_AGENT_OPTIONS_TOML` for agent-specific launch details. OpenClaw and Hermes also accept `DIREXIO_OPENCLAW_COMMAND`, `DIREXIO_HERMES_COMMAND`, `DIREXIO_HERMES_ACP_ADAPTER_COMMAND`, `DIREXIO_OPENCLAW_ACP_ARGS_TOML`, and `DIREXIO_HERMES_ACP_ARGS_TOML`; Hermes custom args are child Hermes args and S6 prefixes the adapter wrapper automatically.
|
|
331
355
|
|
|
332
356
|
`DIREXIO_AGENT_PLATFORM` describes the host runtime following the skill, while `DIREXIO_CC_CONNECT_AGENT` describes the local agent backend that `direxio-connect` should launch. Host runtimes such as Hermes or OpenClaw are not native cc-connect backend types; S6 maps them to the generic ACP backend by default and records `cc_connect_agent=acp`. Override `DIREXIO_CC_CONNECT_AGENT` only when the operator intentionally wants a different local backend.
|
|
333
357
|
|
|
334
|
-
`DIREXIO_AGENT_INSTALL` may be `skip`, `recommend`, or `auto`. Only `auto` attempts to run `npm install -g direxio-connent@latest` and `direxio-connect daemon install --config ~/.direxio/nodes/<service_id>/cc-connect/config.toml --service-name <service_id> --force`; the default `recommend` records and prints the command without mutating local daemon state. An automatic install is reported as installed only when `direxio-connect daemon status --service-name <service_id>` returns `Status: Running` and recent daemon logs do not show ACP session initialization failure; otherwise S6 records `agent_install_status=install_failed`.
|
|
358
|
+
`DIREXIO_AGENT_INSTALL` may be `skip`, `recommend`, or `auto`. Only `auto` attempts to run `npm install -g direxio-connent@latest` and `direxio-connect daemon install --config ~/.direxio/nodes/<service_id>/cc-connect/config.toml --service-name <service_id> --force`; the default `recommend` records and prints the command without mutating local daemon state. An automatic install is reported as installed only when `direxio-connect daemon status --service-name <service_id>` returns `Status: Running` and recent daemon logs do not show ACP session initialization failure; otherwise S6 records `agent_install_status=install_failed`. S6 calls `agent.matrix_session.create` with `agent_token` and retries transient HTTP 000/5xx responses before failing, because the Matrix action can become reachable a few seconds after `/healthz`.
|
|
335
359
|
|
|
336
360
|
Voice input is supported through `direxio-connect` speech-to-text. When `DIREXIO_SPEECH_API_KEY` or a provider-specific key such as `DIREXIO_SPEECH_QWEN_API_KEY`, `OPENAI_API_KEY`, `GROQ_API_KEY`, `DASHSCOPE_API_KEY`, `GEMINI_API_KEY`, or `GOOGLE_API_KEY` is present, S6 writes `[speech] enabled = true` into the generated config. Without an STT key, do not claim voice input is enabled.
|
|
337
361
|
|
|
@@ -374,9 +398,12 @@ DOMAIN=<DOMAIN> bash scripts/orchestrate.sh verify mcp_tools
|
|
|
374
398
|
DOMAIN=<DOMAIN> bash scripts/orchestrate.sh verify mcp_smoke
|
|
375
399
|
```
|
|
376
400
|
|
|
377
|
-
Use `verify runtime` as the normal aggregate check. It runs
|
|
378
|
-
|
|
379
|
-
|
|
401
|
+
Use `verify runtime` as the normal aggregate check. It runs MCP doctor, MCP
|
|
402
|
+
`tools/list`, and read-only backend smoke. It also runs the service-scoped
|
|
403
|
+
connect daemon check when the daemon was expected to be installed; when S6
|
|
404
|
+
recorded `agent_install_status=recommend` or `skip`, the aggregate marks
|
|
405
|
+
`runtime_checks.connect_daemon.status=manual_pending` and does not fail the
|
|
406
|
+
summary for that explicit operator action. The individual commands are useful
|
|
380
407
|
when diagnosing one layer. These commands write `runtime_checks.connect_daemon`,
|
|
381
408
|
`runtime_checks.mcp_doctor`, `runtime_checks.mcp_tools`, and
|
|
382
409
|
`runtime_checks.mcp_smoke` into `state.json` and the operation report.
|
|
@@ -479,7 +506,8 @@ NS nameservers before authoritative DNS can resolve. Never use temporary
|
|
|
479
506
|
**Credential freshness:** The synced `password` and owner `access_token`
|
|
480
507
|
are one-time/volatile values. User login or token exchange can reset them
|
|
481
508
|
on the server. Before reporting the eight-digit app initialization code or using an owner
|
|
482
|
-
`access_token` for API calls,
|
|
509
|
+
`access_token` for owner API calls, or using `agent_token` for
|
|
510
|
+
`agent.matrix_session.create`, rerun the credential sync path or pull the
|
|
483
511
|
latest `/opt/p2p/bootstrap.json` from the server; do not reuse values from
|
|
484
512
|
old chat output, old `state.json`, or stale local `credentials.json`.
|
|
485
513
|
**Runtime detection note:** S6 checks active-process signals before stale
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "direxio-deployer",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Versioned Direxio deployer agent skill and portable deployment orchestration tools.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"scripts/"
|
|
19
19
|
],
|
|
20
20
|
"scripts": {
|
|
21
|
-
"test": "bash tests/npm_skill_distribution_test.sh && bash tests/skill_structure_test.sh && bash tests/s6_wire_local_test.sh && bash tests/render_userdata_remote_nodes_test.sh"
|
|
21
|
+
"test": "bash tests/npm_skill_distribution_test.sh && bash tests/skill_structure_test.sh && bash tests/private_file_permissions_test.sh && bash tests/s6_wire_local_test.sh && bash tests/render_userdata_remote_nodes_test.sh"
|
|
22
22
|
},
|
|
23
23
|
"engines": {
|
|
24
24
|
"node": ">=18"
|
|
@@ -6,6 +6,12 @@ Use this file when installing or updating this skill and when reviewing S6 local
|
|
|
6
6
|
|
|
7
7
|
Prefer a project-local npm-managed install when a project or workspace exists. Install the versioned package, then let the CLI copy the skill bundle into the runtime-specific target:
|
|
8
8
|
|
|
9
|
+
Do not use a generic "install skills <GitHub URL>" instruction for normal users. That can invoke a host's GitHub skill installer and place this repository under the global runtime directory before the npm-managed installer runs. A short user prompt should point the agent back to this npm/project-local rule:
|
|
10
|
+
|
|
11
|
+
```text
|
|
12
|
+
Follow the direxio-deployer README npm/project-local install rule, then deploy Direxio with domain __DOMAIN__.
|
|
13
|
+
```
|
|
14
|
+
|
|
9
15
|
POSIX shells:
|
|
10
16
|
|
|
11
17
|
```bash
|
|
@@ -125,4 +131,4 @@ Use `mcp/codex.toml` for Codex and `mcp/hermes.mcp.json` for Hermes. For OpenCla
|
|
|
125
131
|
|
|
126
132
|
Prefer `DIREXIO_CC_CONNECT_AGENT=<agent>` to choose the local agent that `direxio-connect` should run. Keep `DIREXIO_AGENT_PLATFORM=<runtime>` for auto-detection overrides and legacy host-runtime naming. Use `DIREXIO_AGENT_INSTALL_MODE=cc-connect` only when overriding the default `recommended` mapping explicitly.
|
|
127
133
|
Use `DIREXIO_CC_CONNECT_AGENT_OPTIONS_TOML` for agent-specific options that cannot be represented by `work_dir` or `cmd`; for example `reasonix` requires `serve_url`, `tmux` requires `session`, and generic `acp` requires a command when `DIREXIO_CC_CONNECT_AGENT_CMD` is not enough.
|
|
128
|
-
For OpenClaw Gateway ACP, complete OpenClaw pairing first, then set `DIREXIO_OPENCLAW_ACP_URL`, `DIREXIO_OPENCLAW_ACP_TOKEN_FILE`, and `DIREXIO_OPENCLAW_ACP_SESSION` from the current OpenClaw runtime. S6 writes `["acp", "--url", <url>, "--token-file", <local path>, "--session", <session>]` and converts the token-file with `DIREXIO_LOCAL_PATH_STYLE`. `DIREXIO_OPENCLAW_ACP_ARGS_TOML` replaces the OpenClaw ACP args array only when the runtime needs a fully custom argument list. `DIREXIO_HERMES_ACP_ARGS_TOML` supplies the child Hermes args and keeps the Direxio adapter prefix.
|
|
134
|
+
For OpenClaw Gateway ACP, S6 defaults to `["acp", "--session", "agent:main:main"]` and lets `openclaw acp` auto-discover the Gateway from `~/.openclaw/openclaw.json`. To force an explicit Gateway, complete OpenClaw pairing first, then set all of `DIREXIO_OPENCLAW_ACP_URL`, `DIREXIO_OPENCLAW_ACP_TOKEN_FILE`, and `DIREXIO_OPENCLAW_ACP_SESSION` from the current OpenClaw runtime. S6 writes `["acp", "--url", <url>, "--token-file", <local path>, "--session", <session>]` and converts the token-file with `DIREXIO_LOCAL_PATH_STYLE`. `DIREXIO_OPENCLAW_ACP_ARGS_TOML` replaces the OpenClaw ACP args array only when the runtime needs a fully custom argument list. `DIREXIO_HERMES_ACP_ARGS_TOML` supplies the child Hermes args and keeps the Direxio adapter prefix.
|
|
@@ -55,9 +55,13 @@ bash scripts/pricing-estimate.sh --state ~/.direxio/nodes/<service_id>/state.jso
|
|
|
55
55
|
selection and refreshes it in S3 after the final EC2 instance type is known.
|
|
56
56
|
The estimate includes EC2, gp3 storage, public IPv4, and Route53 hosted-zone
|
|
57
57
|
cost when applicable. It excludes data transfer, TURN relay traffic, domain
|
|
58
|
-
registration, taxes, and AWS credits.
|
|
59
|
-
|
|
60
|
-
|
|
58
|
+
registration, taxes, and AWS credits. When available, check the Free Tier
|
|
59
|
+
account plan with `aws freetier get-account-plan-state --output json`;
|
|
60
|
+
otherwise tell the user that AWS currently advertises 100 USD initial credits
|
|
61
|
+
for new customer accounts plus possible additional credits after Free Tier
|
|
62
|
+
activities. Credit coverage is not guaranteed; verify credits and actual
|
|
63
|
+
charges in AWS Billing Console, and set an AWS Budget or billing alert before
|
|
64
|
+
leaving the node running.
|
|
61
65
|
|
|
62
66
|
## Destroy
|
|
63
67
|
|
|
@@ -141,7 +145,10 @@ When the user or runtime evidence confirms a manual product gate, write it back
|
|
|
141
145
|
to state before regenerating the report. Connect daemon status is a
|
|
142
146
|
service-scoped local bridge check, MCP doctor is a non-polluting runtime check,
|
|
143
147
|
MCP tools is stdio `tools/list` discovery, and MCP smoke is a read-only backend
|
|
144
|
-
call.
|
|
148
|
+
call. In the default `DIREXIO_AGENT_INSTALL=recommend` path, `verify runtime`
|
|
149
|
+
records `connect_daemon=manual_pending` instead of failing the aggregate,
|
|
150
|
+
because daemon installation is an explicit operator action. These checks are
|
|
151
|
+
not the full runtime product gate:
|
|
145
152
|
|
|
146
153
|
```bash
|
|
147
154
|
DOMAIN=__DOMAIN__ bash scripts/orchestrate.sh verify runtime
|
|
@@ -74,7 +74,7 @@ DIREXIO_CREDENTIALS_FILE=~/.direxio/nodes/<service_id>/credentials.json direxio-
|
|
|
74
74
|
|
|
75
75
|
## cc-connect Matrix Bridge
|
|
76
76
|
|
|
77
|
-
S6 calls `agent.matrix_session.create` with the owner
|
|
77
|
+
S6 calls `agent.matrix_session.create` with the backend `agent_token`, not the owner `access_token`. Current message-server builds must return a Matrix session for `@agent:<server>`, not for `@owner:<server>`. S6 retries transient HTTP 000/5xx responses before failing, because the Matrix action can become reachable shortly after `/healthz`. The resulting session is stored at:
|
|
78
78
|
|
|
79
79
|
```text
|
|
80
80
|
~/.direxio/nodes/<service_id>/cc-connect/matrix-session.json
|
|
@@ -112,9 +112,9 @@ DIREXIO_<AGENT>_COMMAND=<optional agent-specific executable path>
|
|
|
112
112
|
DIREXIO_CC_CONNECT_AGENT_OPTIONS_TOML=<optional extra TOML under projects.agent.options>
|
|
113
113
|
DIREXIO_OPENCLAW_COMMAND=<optional OpenClaw executable path>
|
|
114
114
|
DIREXIO_HERMES_COMMAND=<optional Hermes executable path>
|
|
115
|
-
DIREXIO_OPENCLAW_ACP_URL=<
|
|
116
|
-
DIREXIO_OPENCLAW_ACP_TOKEN_FILE=<
|
|
117
|
-
DIREXIO_OPENCLAW_ACP_SESSION=<
|
|
115
|
+
DIREXIO_OPENCLAW_ACP_URL=<optional explicit OpenClaw gateway URL>
|
|
116
|
+
DIREXIO_OPENCLAW_ACP_TOKEN_FILE=<optional explicit OpenClaw ACP token file>
|
|
117
|
+
DIREXIO_OPENCLAW_ACP_SESSION=<optional OpenClaw ACP session; defaults to agent:main:main>
|
|
118
118
|
DIREXIO_OPENCLAW_ACP_ARGS_TOML=<optional OpenClaw ACP TOML array>
|
|
119
119
|
DIREXIO_HERMES_ACP_ARGS_TOML=<optional Hermes ACP TOML array>
|
|
120
120
|
DIREXIO_CC_CONNECT_NPM_PACKAGE=direxio-connent@latest
|
|
@@ -131,13 +131,13 @@ DIREXIO_SPEECH_LANGUAGE=zh
|
|
|
131
131
|
Defaults:
|
|
132
132
|
|
|
133
133
|
- `DIREXIO_CC_CONNECT_AGENT` is the preferred explicit selector. It accepts every connent/connect agent: `acp`, `antigravity`, `claudecode`, `codex`, `copilot`, `cursor`, `devin`, `gemini`, `iflow`, `kimi`, `opencode`, `pi`, `qoder`, `reasonix`, and `tmux`.
|
|
134
|
-
- `DIREXIO_AGENT_PLATFORM=auto` detects the local agent runtime and maps it to a `direxio-connect` agent type only when it can identify one unambiguously. OpenClaw and Hermes map to the generic `acp` connect agent. OpenClaw
|
|
134
|
+
- `DIREXIO_AGENT_PLATFORM=auto` detects the local agent runtime and maps it to a `direxio-connect` agent type only when it can identify one unambiguously. OpenClaw and Hermes map to the generic `acp` connect agent. OpenClaw uses `openclaw acp --session agent:main:main` by default and lets OpenClaw discover its Gateway config; Hermes uses the `direxio-connect hermes-acp-adapter -- hermes acp` compatibility wrapper by default.
|
|
135
135
|
- `DIREXIO_LOCAL_PATH_STYLE=windows` writes Windows-compatible `data_dir`, `work_dir`, config paths, and install commands. `scripts/orchestrate.ps1` sets this automatically. Linux, macOS, and WSL Bash runs should leave the default `posix` style. Windows Git Bash/MSYS2 users who run `scripts/orchestrate.sh` directly must set `DIREXIO_LOCAL_PATH_STYLE=windows` when the local bridge is a Windows process.
|
|
136
136
|
- `DIREXIO_CC_CONNECT_AGENT_CMD` writes `cmd = "<path>"` into `[projects.agent.options]`. Agent-specific forms such as `DIREXIO_CODEX_COMMAND`, `DIREXIO_CLAUDE_CODE_COMMAND`, `DIREXIO_GEMINI_COMMAND`, `DIREXIO_OPENCODE_COMMAND`, `DIREXIO_QODERCLI_COMMAND`, and `DIREXIO_OPENCLAW_COMMAND` are also accepted. For Hermes, `DIREXIO_HERMES_COMMAND` selects the child Hermes executable behind the adapter, while `DIREXIO_HERMES_ACP_ADAPTER_COMMAND` overrides the adapter command itself.
|
|
137
137
|
- `DIREXIO_CC_CONNECT_AGENT_OPTIONS_TOML` appends agent-specific options under `[projects.agent.options]`; use it for agents with required non-command options such as `reasonix` (`serve_url`) or `tmux` (`session`).
|
|
138
|
-
- OpenClaw Gateway ACP
|
|
138
|
+
- OpenClaw Gateway ACP auto-detects the Gateway from `~/.openclaw/openclaw.json` when `DIREXIO_OPENCLAW_ACP_URL` and `DIREXIO_OPENCLAW_ACP_TOKEN_FILE` are unset. It uses `DIREXIO_OPENCLAW_ACP_SESSION` when provided, otherwise `agent:main:main`. To force explicit Gateway settings, complete OpenClaw pairing first and set all three real values: `DIREXIO_OPENCLAW_ACP_URL`, `DIREXIO_OPENCLAW_ACP_TOKEN_FILE`, and `DIREXIO_OPENCLAW_ACP_SESSION`.
|
|
139
139
|
- `DIREXIO_OPENCLAW_ACP_ARGS_TOML` replaces the generated OpenClaw ACP args array, for example `["acp", "--url", "wss://gateway.example.test:18789", "--token-file", "$HOME/.openclaw/gateway.token", "--session", "agent:main:main"]`. `DIREXIO_HERMES_ACP_ARGS_TOML` supplies the child Hermes args; S6 prefixes `["hermes-acp-adapter", "--", "<hermes-command>"]` automatically.
|
|
140
|
-
- `DIREXIO_AGENT_INSTALL=recommend` prints and records the command only.
|
|
140
|
+
- `DIREXIO_AGENT_INSTALL=recommend` prints and records the command only. `verify runtime` records the daemon check as `manual_pending` in this mode and still verifies MCP doctor/tools/smoke.
|
|
141
141
|
- `DIREXIO_AGENT_INSTALL=auto` runs `npm install -g direxio-connent@latest` and then installs the `direxio-connect` daemon with the generated config and `--service-name <service_id>`. It is recorded as installed only when `direxio-connect daemon status --service-name <service_id>` reports `Status: Running` and recent daemon logs do not show ACP session initialization failure; otherwise S6 records `agent_install_status=install_failed`.
|
|
142
142
|
- `DIREXIO_AGENT_INSTALL_MODE=recommended` maps every supported local runtime to `cc-connect`.
|
|
143
143
|
- Speech defaults to `DIREXIO_SPEECH_PROVIDER=openai` and `DIREXIO_SPEECH_LANGUAGE=zh`. Provider-specific keys are also accepted: `DIREXIO_SPEECH_OPENAI_API_KEY` or `OPENAI_API_KEY`, `DIREXIO_SPEECH_GROQ_API_KEY` or `GROQ_API_KEY`, `DIREXIO_SPEECH_QWEN_API_KEY` or `DASHSCOPE_API_KEY`, and `DIREXIO_SPEECH_GEMINI_API_KEY`, `GEMINI_API_KEY`, or `GOOGLE_API_KEY`. Set `DIREXIO_SPEECH_ENABLED=false` to suppress speech config generation even when a key exists.
|
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
- **S2_DOMAIN**: 确认正式长期域名和 Matrix `server_name` 不可逆绑定。
|
|
10
10
|
- **S3_PROVISION**: 创建 EC2、密钥对、安全组、Elastic IP,按 DNS 模式处理 Route53 hosted zone/A 记录或等待外部 DNS,渲染 cloud-init。默认镜像 `MESSAGE_SERVER_IMAGE=direxio/message-server:latest`。
|
|
11
11
|
- **S4_BOOTSTRAP_STACK**: 等 cloud-init 安装 Docker 并启动 `postgres:18 + message-server + caddy + coturn`,轮询 `https://<domain>/healthz`。
|
|
12
|
-
- **S5_INIT_TOKENS**: SSH 读取云端 `init-tokens.sh` 生成的 `/opt/p2p/bootstrap.json`,归一化 `password`、`access_token`、`agent_token`、真实 `agent_room_id`。云端脚本会先调用 `portal.bootstrap
|
|
13
|
-
- **S6_WIRE_LOCAL**:
|
|
12
|
+
- **S5_INIT_TOKENS**: SSH 读取云端 `init-tokens.sh` 生成的 `/opt/p2p/bootstrap.json`,归一化 `password`、`access_token`、`agent_token`、真实 `agent_room_id`。云端脚本会先调用 `portal.bootstrap`,用 `agent_token` 创建 `@agent:<server>` Matrix session,再用 owner Matrix token 创建房间并邀请/加入 agent,最后回写真正的 agent room。`password`、owner `access_token` 和 `agent_token` 按一次性/易失凭据处理;需要登录或用 token 调接口前,必须重新从服务器拉取最新 `/opt/p2p/bootstrap.json`,不要复用旧输出。
|
|
13
|
+
- **S6_WIRE_LOCAL**: 写本地凭据、用 `agent_token` 创建 `@agent:<server>` Matrix session、写 `cc-connect/config.toml`,写 MCP 配置片段,并按策略安装或推荐 `direxio-connect`。
|
|
14
14
|
- **S7_VERIFY_E2E**: 验证 `/_p2p`、Matrix versions、well-known、owner.json+CORS、TURN。
|
|
15
15
|
|
|
16
16
|
## 云端 compose
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
每次重部署或清空数据卷后,`password`、owner `access_token`、`agent_token` 和 cc-connect Matrix session 都会变化。状态机 S6 会自动回填;手动恢复时按这里检查。
|
|
4
4
|
|
|
5
|
-
从服务端同步过来的 `password` 和 owner `access_token` 必须按一次性/易失凭据处理。`password` 是后端字段名,对用户展示时必须叫八位 App 初始化码。用户完成初始化或 token exchange 后,服务端可能立刻重置这些值;任何需要再次获取初始化码,或需要用 `access_token` 调
|
|
5
|
+
从服务端同步过来的 `password` 和 owner `access_token` 必须按一次性/易失凭据处理。`password` 是后端字段名,对用户展示时必须叫八位 App 初始化码。用户完成初始化或 token exchange 后,服务端可能立刻重置这些值;任何需要再次获取初始化码,或需要用 `access_token` 调 owner 身份 API/Matrix Client API,或需要用 `agent_token` 调 `agent.matrix_session.create` 的操作,都必须先重新从服务器拉取最新 `/opt/p2p/bootstrap.json`,再更新本地 `credentials.json`。不要复用聊天记录、旧 `state.json`、旧 `credentials.json` 或历史部署输出里的 password/access token。
|
|
6
6
|
|
|
7
7
|
现有节点执行 `scripts/update.sh` 或 `scripts/reset-app-data.sh` 后,本地旧证据也必须作废。脚本会清掉旧 `password`、`access_token`、`agent_token`、`agent_room_id`、`user_confirmations` 和 `runtime_checks`,把 `agent_install_status` 标成 `refresh_pending`,并只在 `WorkDir` 匹配当前 service 时停止对应的本地 bridge(stops only the matching service-scoped direxio-connect daemon),再把 S4-S7 标回 pending。这样旧的用户确认、MCP discovery、Agent runtime probe 或旧 bridge 安装状态不会被误用到更新/重置后的节点。`status` 会显示 `Local refresh:`,提醒 update/reset 已经清掉旧 credentials、user confirmations、runtime checks 和 bridge install proof;下一步必须 rerun the deployment workflow to refresh S4-S7, local credentials, MCP snippets, and runtime checks。后续必须续跑 `scripts/orchestrate.sh`,让 S5/S6/S7 和 `verify runtime` 重新写入当前证据。
|
|
8
8
|
|
|
@@ -109,7 +109,7 @@ Use the Git Bash `$HOME` path for files generated by the deployer. If running `d
|
|
|
109
109
|
|
|
110
110
|
## EC2 SSH Key Paths
|
|
111
111
|
|
|
112
|
-
SSH key files are written with Windows-compatible paths such as `C:/Users/.../.direxio/deploy/p2p-*.pem`. The SSH command printed in the delivery summary works in Git Bash. If using PowerShell or cmd, convert forward slashes to backslashes.
|
|
112
|
+
SSH key files are written with Windows-compatible paths such as `C:/Users/.../.direxio/deploy/p2p-*.pem`. The deployer removes inherited Windows ACLs where possible so OpenSSH does not reject the private key as too open. The SSH command printed in the delivery summary works in Git Bash. If using PowerShell or cmd, convert forward slashes to backslashes.
|
|
113
113
|
|
|
114
114
|
## Verifying Deployment
|
|
115
115
|
|
|
@@ -149,7 +149,7 @@ PY
|
|
|
149
149
|
}
|
|
150
150
|
|
|
151
151
|
ensure_agent_room() {
|
|
152
|
-
local owner_token agent_user session room_resp join_resp
|
|
152
|
+
local owner_token agent_auth_token agent_user session room_resp join_resp matrix_agent_token room_id room_path
|
|
153
153
|
if copy_bootstrap_file && bootstrap_has_real_agent_room "$BOOTSTRAP_FILE"; then
|
|
154
154
|
log "agent_room_id is already present."
|
|
155
155
|
return 0
|
|
@@ -160,15 +160,20 @@ ensure_agent_room() {
|
|
|
160
160
|
log "FATAL: access_token is missing; cannot create agent room"
|
|
161
161
|
return 1
|
|
162
162
|
fi
|
|
163
|
+
agent_auth_token=$(json_string agent_token "$BOOTSTRAP_FILE")
|
|
164
|
+
if [ -z "$agent_auth_token" ]; then
|
|
165
|
+
log "FATAL: agent_token is missing; cannot create agent Matrix session"
|
|
166
|
+
return 1
|
|
167
|
+
fi
|
|
163
168
|
agent_user="@agent:${DOMAIN}"
|
|
164
169
|
session=$(mktemp)
|
|
165
|
-
if ! container_post_json "/_p2p/command" '{"action":"agent.matrix_session.create","params":{"device_id":"DIREXIO_DEPLOY_BOOTSTRAP"}}' "$
|
|
170
|
+
if ! container_post_json "/_p2p/command" '{"action":"agent.matrix_session.create","params":{"device_id":"DIREXIO_DEPLOY_BOOTSTRAP"}}' "$agent_auth_token" > "$session" 2>/dev/null; then
|
|
166
171
|
log "FATAL: agent.matrix_session.create failed: $(head -c 160 "$session" 2>/dev/null)"
|
|
167
172
|
rm -f "$session"
|
|
168
173
|
return 1
|
|
169
174
|
fi
|
|
170
|
-
|
|
171
|
-
if [ -z "$
|
|
175
|
+
matrix_agent_token=$(json_string access_token "$session")
|
|
176
|
+
if [ -z "$matrix_agent_token" ]; then
|
|
172
177
|
log "FATAL: agent.matrix_session.create did not return access_token: $(head -c 160 "$session" 2>/dev/null)"
|
|
173
178
|
rm -f "$session"
|
|
174
179
|
return 1
|
|
@@ -190,7 +195,7 @@ ensure_agent_room() {
|
|
|
190
195
|
|
|
191
196
|
room_path=$(matrix_room_path "$room_id")
|
|
192
197
|
join_resp=$(mktemp)
|
|
193
|
-
if ! container_post_json "/_matrix/client/v3/rooms/${room_path}/join" '{}' "$
|
|
198
|
+
if ! container_post_json "/_matrix/client/v3/rooms/${room_path}/join" '{}' "$matrix_agent_token" > "$join_resp" 2>/dev/null; then
|
|
194
199
|
log "FATAL: agent join failed for ${room_id}: $(head -c 160 "$join_resp" 2>/dev/null)"
|
|
195
200
|
rm -f "$join_resp"
|
|
196
201
|
return 1
|
package/scripts/lib/state.sh
CHANGED
|
@@ -46,6 +46,28 @@ is_yes() {
|
|
|
46
46
|
esac
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
restrict_private_file() {
|
|
50
|
+
local file=$1 uname_s win_file user user_domain
|
|
51
|
+
chmod 600 "$file" 2>/dev/null || true
|
|
52
|
+
uname_s=$(uname -s 2>/dev/null || printf unknown)
|
|
53
|
+
case "$uname_s" in
|
|
54
|
+
MINGW*|MSYS*|CYGWIN*)
|
|
55
|
+
command -v icacls >/dev/null 2>&1 || return 0
|
|
56
|
+
win_file=$file
|
|
57
|
+
if command -v cygpath >/dev/null 2>&1; then
|
|
58
|
+
win_file=$(cygpath -w "$file")
|
|
59
|
+
fi
|
|
60
|
+
user=$(cmd.exe /c whoami 2>/dev/null | tr -d '\r' | tail -n 1 || true)
|
|
61
|
+
user_domain=${USERDOMAIN:-}
|
|
62
|
+
icacls "$win_file" /inheritance:r >/dev/null 2>&1 || true
|
|
63
|
+
icacls "$win_file" /remove:g \
|
|
64
|
+
"Users" "Authenticated Users" "Everyone" "CodexSandboxUsers" \
|
|
65
|
+
"${user_domain}\\CodexSandboxUsers" >/dev/null 2>&1 || true
|
|
66
|
+
[ -n "$user" ] && icacls "$win_file" /grant:r "$user:R" >/dev/null 2>&1 || true
|
|
67
|
+
;;
|
|
68
|
+
esac
|
|
69
|
+
}
|
|
70
|
+
|
|
49
71
|
# Initialize state.json for a new deployment.
|
|
50
72
|
state_init() {
|
|
51
73
|
mkdir -p "$P2P_WORKDIR"
|
|
@@ -26,6 +26,7 @@ child.stderr.on("data", (chunk) => {
|
|
|
26
26
|
child.stdout.on("data", (chunk) => {
|
|
27
27
|
stdout = Buffer.concat([stdout, chunk]);
|
|
28
28
|
readFrames();
|
|
29
|
+
if (completed) return;
|
|
29
30
|
if (responses.has(2)) {
|
|
30
31
|
const response = responses.get(2);
|
|
31
32
|
const tools = Array.isArray(response?.result?.tools) ? response.result.tools : [];
|
|
@@ -60,35 +61,78 @@ send({ jsonrpc: "2.0", method: "notifications/initialized", params: {} });
|
|
|
60
61
|
send({ jsonrpc: "2.0", id: 2, method: "tools/list", params: {} });
|
|
61
62
|
|
|
62
63
|
function send(message) {
|
|
63
|
-
|
|
64
|
+
const body = JSON.stringify(message);
|
|
65
|
+
child.stdin.write(`Content-Length: ${Buffer.byteLength(body, "utf8")}\r\n\r\n${body}`);
|
|
64
66
|
}
|
|
65
67
|
|
|
66
68
|
function readFrames() {
|
|
67
69
|
while (true) {
|
|
68
|
-
|
|
69
|
-
if (
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
70
|
+
if (stdout.length === 0) return;
|
|
71
|
+
if (stdout[0] === 10 || stdout[0] === 13) {
|
|
72
|
+
stdout = stdout.subarray(1);
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
if (startsWithHeader(stdout, "Content-Length:")) {
|
|
76
|
+
const header = readHeader(stdout);
|
|
77
|
+
if (!header) return;
|
|
78
|
+
const contentLength = parseContentLength(header.text);
|
|
79
|
+
if (!Number.isSafeInteger(contentLength) || contentLength < 0) {
|
|
80
|
+
finishWithError("MCP response frame is missing a valid Content-Length header");
|
|
81
|
+
return;
|
|
80
82
|
}
|
|
83
|
+
const messageEnd = header.bodyStart + contentLength;
|
|
84
|
+
if (stdout.length < messageEnd) return;
|
|
85
|
+
const body = stdout.subarray(header.bodyStart, messageEnd).toString("utf8");
|
|
86
|
+
stdout = stdout.subarray(messageEnd);
|
|
87
|
+
handleMessage(body);
|
|
88
|
+
if (completed) return;
|
|
89
|
+
continue;
|
|
81
90
|
}
|
|
91
|
+
|
|
82
92
|
const lineEnd = stdout.indexOf("\n");
|
|
83
93
|
if (lineEnd < 0) return;
|
|
84
94
|
const line = stdout.subarray(0, lineEnd).toString("utf8").replace(/\r$/, "");
|
|
85
95
|
stdout = stdout.subarray(lineEnd + 1);
|
|
86
96
|
if (line.length === 0) continue;
|
|
87
|
-
|
|
97
|
+
handleMessage(line);
|
|
98
|
+
if (completed) return;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function startsWithHeader(buffer, header) {
|
|
103
|
+
return buffer.subarray(0, header.length).toString("utf8").toLowerCase() === header.toLowerCase();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function readHeader(buffer) {
|
|
107
|
+
let marker = "\r\n\r\n";
|
|
108
|
+
let headerEnd = buffer.indexOf(marker);
|
|
109
|
+
if (headerEnd < 0) {
|
|
110
|
+
marker = "\n\n";
|
|
111
|
+
headerEnd = buffer.indexOf(marker);
|
|
112
|
+
}
|
|
113
|
+
if (headerEnd < 0) return null;
|
|
114
|
+
return {
|
|
115
|
+
text: buffer.subarray(0, headerEnd).toString("utf8"),
|
|
116
|
+
bodyStart: headerEnd + marker.length
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function parseContentLength(headerText) {
|
|
121
|
+
for (const line of headerText.split(/\r?\n/)) {
|
|
122
|
+
const match = /^content-length:\s*(\d+)\s*$/i.exec(line);
|
|
123
|
+
if (match) return Number.parseInt(match[1], 10);
|
|
88
124
|
}
|
|
125
|
+
return NaN;
|
|
89
126
|
}
|
|
90
127
|
|
|
91
|
-
function
|
|
128
|
+
function handleMessage(raw) {
|
|
129
|
+
let message;
|
|
130
|
+
try {
|
|
131
|
+
message = JSON.parse(raw);
|
|
132
|
+
} catch (error) {
|
|
133
|
+
finishWithError(`invalid MCP JSON response: ${error.message}`);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
92
136
|
if (typeof message.id !== "undefined") {
|
|
93
137
|
responses.set(message.id, message);
|
|
94
138
|
}
|
package/scripts/orchestrate.sh
CHANGED
|
@@ -395,6 +395,25 @@ ensure_cost_estimate() {
|
|
|
395
395
|
else
|
|
396
396
|
warn "Could not write AWS cost estimate. Continue only after giving the user a manual billing estimate."
|
|
397
397
|
fi
|
|
398
|
+
ensure_free_tier_credit_notice
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
ensure_free_tier_credit_notice() {
|
|
402
|
+
local output plan_status plan_type amount unit expires
|
|
403
|
+
if output=$(aws freetier get-account-plan-state --output json 2>/dev/null); then
|
|
404
|
+
plan_type=$(printf '%s\n' "$output" | json_stdin_get accountPlanType "unknown" 2>/dev/null)
|
|
405
|
+
plan_status=$(printf '%s\n' "$output" | json_stdin_get accountPlanStatus "unknown" 2>/dev/null)
|
|
406
|
+
amount=$(printf '%s\n' "$output" | json_stdin_get accountPlanRemainingCredits.amount "" 2>/dev/null)
|
|
407
|
+
unit=$(printf '%s\n' "$output" | json_stdin_get accountPlanRemainingCredits.unit "USD" 2>/dev/null)
|
|
408
|
+
expires=$(printf '%s\n' "$output" | json_stdin_get accountPlanExpirationDate "" 2>/dev/null)
|
|
409
|
+
if [ -n "$amount" ]; then
|
|
410
|
+
log "AWS Free Tier plan: type=${plan_type:-unknown}, status=${plan_status:-unknown}, remaining_credits=${amount} ${unit:-USD}${expires:+, expires=$expires}."
|
|
411
|
+
warn "Credits can reduce actual charges, but AWS resources still accrue charges until destroyed; verify credit coverage in AWS Billing Console."
|
|
412
|
+
return 0
|
|
413
|
+
fi
|
|
414
|
+
fi
|
|
415
|
+
warn "AWS new customer accounts may include Free Tier credits, currently advertised as 100 USD initial credits plus possible additional credits."
|
|
416
|
+
warn "Credits may cover a small trial deployment, but coverage is account-specific; verify credits in AWS Billing Console and destroy the node when finished."
|
|
398
417
|
}
|
|
399
418
|
|
|
400
419
|
precheck_new_deploy_domain_env() {
|
|
@@ -947,15 +966,35 @@ runtime_check_status() {
|
|
|
947
966
|
json_get "$STATE_JSON" "runtime_checks.$check.status" "not_run"
|
|
948
967
|
}
|
|
949
968
|
|
|
969
|
+
runtime_status_counts_as_failure() {
|
|
970
|
+
local status=$1
|
|
971
|
+
case "$status" in
|
|
972
|
+
passed|manual_pending|skipped) return 1 ;;
|
|
973
|
+
*) return 0 ;;
|
|
974
|
+
esac
|
|
975
|
+
}
|
|
976
|
+
|
|
950
977
|
cmd_verify_runtime() {
|
|
951
978
|
[ -f "$STATE_JSON" ] || {
|
|
952
979
|
warn "state.json not found: $STATE_JSON"
|
|
953
980
|
return 1
|
|
954
981
|
}
|
|
955
982
|
|
|
956
|
-
local rc=0 failed_count=0 connect_status doctor_status tools_status smoke_status status
|
|
983
|
+
local rc=0 failed_count=0 connect_status doctor_status tools_status smoke_status status install_status install_policy service_name
|
|
957
984
|
|
|
958
|
-
|
|
985
|
+
install_status=$(json_get "$STATE_JSON" agent_install_status)
|
|
986
|
+
install_policy=$(json_get "$STATE_JSON" agent_install_policy)
|
|
987
|
+
service_name=$(json_get "$STATE_JSON" agent_service_id)
|
|
988
|
+
[ -n "$service_name" ] || service_name=$(json_get "$STATE_JSON" domain)
|
|
989
|
+
if [ "$install_status" = "recommend" ] || { [ "$install_status" = "skip" ] && [ "${install_policy:-skip}" = "skip" ]; }; then
|
|
990
|
+
state_set_object runtime_checks.connect_daemon \
|
|
991
|
+
status=manual_pending \
|
|
992
|
+
"ts=$(_now)" \
|
|
993
|
+
"evidence=direxio-connect daemon install is an explicit operator action for policy=$install_status" \
|
|
994
|
+
"service_name=${service_name:-cc-connect}"
|
|
995
|
+
else
|
|
996
|
+
cmd_verify_connect_daemon >/dev/null || rc=1
|
|
997
|
+
fi
|
|
959
998
|
cmd_verify_mcp_doctor >/dev/null || rc=1
|
|
960
999
|
cmd_verify_mcp_tools >/dev/null || rc=1
|
|
961
1000
|
cmd_verify_mcp_smoke >/dev/null || rc=1
|
|
@@ -966,7 +1005,7 @@ cmd_verify_runtime() {
|
|
|
966
1005
|
smoke_status=$(runtime_check_status mcp_smoke)
|
|
967
1006
|
|
|
968
1007
|
for status in "$connect_status" "$doctor_status" "$tools_status" "$smoke_status"; do
|
|
969
|
-
|
|
1008
|
+
runtime_status_counts_as_failure "$status" && failed_count=$((failed_count + 1))
|
|
970
1009
|
done
|
|
971
1010
|
|
|
972
1011
|
if [ "$failed_count" -eq 0 ]; then
|
|
@@ -57,7 +57,7 @@ run_phase() {
|
|
|
57
57
|
if [ -z "$(res_get key_name)" ]; then
|
|
58
58
|
log "Creating key pair $name ..."
|
|
59
59
|
aws ec2 create-key-pair --key-name "$name" --query KeyMaterial --output text > "$keyfile"
|
|
60
|
-
|
|
60
|
+
restrict_private_file "$keyfile"
|
|
61
61
|
res_set key_name "$name"; res_set key_file "$keyfile"
|
|
62
62
|
else
|
|
63
63
|
log "Key pair already exists; skipping."; keyfile=$(res_get key_file)
|
|
@@ -572,15 +572,21 @@ _openclaw_acp_args_toml() {
|
|
|
572
572
|
url=${DIREXIO_OPENCLAW_ACP_URL:-}
|
|
573
573
|
token_file=${DIREXIO_OPENCLAW_ACP_TOKEN_FILE:-}
|
|
574
574
|
session=${DIREXIO_OPENCLAW_ACP_SESSION:-}
|
|
575
|
-
[ -n "$url" ]
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
575
|
+
if [ -n "$url" ] && [ -n "$token_file" ] && [ -n "$session" ]; then
|
|
576
|
+
token_file=$(_local_connect_path "$token_file")
|
|
577
|
+
_toml_array acp --url "$url" --token-file "$token_file" --session "$session"
|
|
578
|
+
return 0
|
|
579
|
+
fi
|
|
580
|
+
if [ -n "$url" ] || [ -n "$token_file" ]; then
|
|
581
|
+
[ -n "$url" ] || missing="${missing} DIREXIO_OPENCLAW_ACP_URL"
|
|
582
|
+
[ -n "$token_file" ] || missing="${missing} DIREXIO_OPENCLAW_ACP_TOKEN_FILE"
|
|
583
|
+
[ -n "$session" ] || missing="${missing} DIREXIO_OPENCLAW_ACP_SESSION"
|
|
584
|
+
fail "OpenClaw ACP explicit Gateway settings are incomplete:${missing}. Set all of DIREXIO_OPENCLAW_ACP_URL, DIREXIO_OPENCLAW_ACP_TOKEN_FILE, and DIREXIO_OPENCLAW_ACP_SESSION; otherwise leave URL/token-file unset so openclaw acp can auto-detect from its config."
|
|
580
585
|
return 1
|
|
581
586
|
fi
|
|
582
|
-
|
|
583
|
-
|
|
587
|
+
# Fallback: OpenClaw acp auto-discovers gateway from ~/.openclaw/openclaw.json.
|
|
588
|
+
warn "OpenClaw ACP: Gateway URL/token-file not set; using session '${session:-agent:main:main}' and letting openclaw acp auto-detect the Gateway from its config."
|
|
589
|
+
_toml_array acp --session "${session:-agent:main:main}"
|
|
584
590
|
}
|
|
585
591
|
|
|
586
592
|
_hermes_acp_args_toml() {
|
|
@@ -879,25 +885,51 @@ EOF
|
|
|
879
885
|
}
|
|
880
886
|
|
|
881
887
|
_create_cc_connect_matrix_session() {
|
|
882
|
-
local asurl=$1
|
|
888
|
+
local asurl=$1 agent_auth_token=$2 device_id=$3 out=$4 body code http_body
|
|
889
|
+
local max_attempts interval attempt preview
|
|
883
890
|
body=$(json_build matrix-session-create "$device_id")
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
891
|
+
max_attempts=${DIREXIO_MATRIX_SESSION_CREATE_MAX:-4}
|
|
892
|
+
interval=${DIREXIO_MATRIX_SESSION_RETRY_INTERVAL:-2}
|
|
893
|
+
attempt=1
|
|
894
|
+
while [ "$attempt" -le "$max_attempts" ]; do
|
|
895
|
+
http_body=$(mktemp)
|
|
896
|
+
code=$(curl -sk \
|
|
897
|
+
--connect-timeout "${DIREXIO_MATRIX_SESSION_CURL_CONNECT_TIMEOUT:-10}" \
|
|
898
|
+
--max-time "${DIREXIO_MATRIX_SESSION_CURL_MAX_TIME:-20}" \
|
|
899
|
+
-o "$http_body" -w '%{http_code}' -X POST "$asurl/_p2p/command" \
|
|
900
|
+
-H 'Content-Type: application/json' \
|
|
901
|
+
-H "Authorization: Bearer $agent_auth_token" \
|
|
902
|
+
-d "$body" 2>/dev/null || true)
|
|
903
|
+
if [ "$code" = "200" ]; then
|
|
904
|
+
if ! json_assert "$http_body" matrix-session >/dev/null; then
|
|
905
|
+
warn "agent.matrix_session.create response is missing Matrix session fields: $(head -c 200 "$http_body" 2>/dev/null)"
|
|
906
|
+
rm -f "$http_body"
|
|
907
|
+
return 1
|
|
908
|
+
fi
|
|
909
|
+
mv "$http_body" "$out"
|
|
910
|
+
chmod 600 "$out" 2>/dev/null || true
|
|
911
|
+
return 0
|
|
912
|
+
fi
|
|
913
|
+
preview=$(head -c 200 "$http_body" 2>/dev/null || true)
|
|
896
914
|
rm -f "$http_body"
|
|
915
|
+
case "${code:-000}" in
|
|
916
|
+
000|5*)
|
|
917
|
+
if [ "$attempt" -lt "$max_attempts" ]; then
|
|
918
|
+
warn "agent.matrix_session.create returned HTTP ${code:-000} on attempt $attempt/$max_attempts; retrying."
|
|
919
|
+
sleep "$interval"
|
|
920
|
+
attempt=$((attempt + 1))
|
|
921
|
+
continue
|
|
922
|
+
fi
|
|
923
|
+
;;
|
|
924
|
+
401)
|
|
925
|
+
warn "agent.matrix_session.create rejected agent_token. Refresh bootstrap credentials or deploy a message-server build that allows agent_token for this action."
|
|
926
|
+
;;
|
|
927
|
+
*) ;;
|
|
928
|
+
esac
|
|
929
|
+
warn "agent.matrix_session.create returned HTTP ${code:-000}: $preview"
|
|
897
930
|
return 1
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
chmod 600 "$out" 2>/dev/null || true
|
|
931
|
+
done
|
|
932
|
+
return 1
|
|
901
933
|
}
|
|
902
934
|
|
|
903
935
|
_write_cc_connect_config() {
|
|
@@ -1328,7 +1360,7 @@ run_phase() {
|
|
|
1328
1360
|
|
|
1329
1361
|
mkdir -p "$workspace"
|
|
1330
1362
|
mkdir -p "$cc_runtime_dir"
|
|
1331
|
-
if ! _create_cc_connect_matrix_session "$asurl" "$
|
|
1363
|
+
if ! _create_cc_connect_matrix_session "$asurl" "$token" "DIREXIO_CC_CONNECT_${node_id}" "$cc_session"; then
|
|
1332
1364
|
phase_set S6_WIRE_LOCAL failed "agent Matrix session creation failed"
|
|
1333
1365
|
fail "failed to create cc-connect Matrix session via agent.matrix_session.create."
|
|
1334
1366
|
fi
|