grix-connector 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +149 -0
- package/dist/adapter/acp/acp-adapter.js +13 -0
- package/dist/adapter/acp/index.js +1 -0
- package/dist/adapter/acp/usage-parser.js +1 -0
- package/dist/adapter/claude/activity-status-manager.js +1 -0
- package/dist/adapter/claude/channel-notification.js +1 -0
- package/dist/adapter/claude/claude-adapter.js +15 -0
- package/dist/adapter/claude/claude-bridge-server.js +1 -0
- package/dist/adapter/claude/claude-tools.js +1 -0
- package/dist/adapter/claude/claude-worker-client.js +1 -0
- package/dist/adapter/claude/index.js +1 -0
- package/dist/adapter/claude/interaction-protocol.js +1 -0
- package/dist/adapter/claude/mcp-http-launcher.js +2 -0
- package/dist/adapter/claude/model-list.js +1 -0
- package/dist/adapter/claude/protocol-contract.js +1 -0
- package/dist/adapter/claude/result-timeout.js +1 -0
- package/dist/adapter/claude/skill-scanner.js +2 -0
- package/dist/adapter/claude/usage-parser.js +3 -0
- package/dist/adapter/codewhale/codewhale-adapter.js +6 -0
- package/dist/adapter/codewhale/index.js +1 -0
- package/dist/adapter/codex/codex-bridge.js +10 -0
- package/dist/adapter/codex/codex-trust.js +8 -0
- package/dist/adapter/codex/index.js +1 -0
- package/dist/adapter/codex/usage-parser.js +1 -0
- package/dist/adapter/cursor/cursor-adapter.js +8 -0
- package/dist/adapter/cursor/index.js +1 -0
- package/dist/adapter/deepseek/deepseek-adapter.js +6 -0
- package/dist/adapter/deepseek/index.js +1 -0
- package/dist/adapter/index.js +1 -0
- package/dist/adapter/opencode/index.js +1 -0
- package/dist/adapter/opencode/opencode-adapter.js +8 -0
- package/dist/adapter/opencode/opencode-transport.js +5 -0
- package/dist/adapter/opencode/opencode-types.js +0 -0
- package/dist/adapter/openhuman/index.js +1 -0
- package/dist/adapter/openhuman/openhuman-adapter.js +7 -0
- package/dist/adapter/openhuman/openhuman-transport.js +1 -0
- package/dist/adapter/openhuman/openhuman-types.js +0 -0
- package/dist/adapter/pi/index.js +1 -0
- package/dist/adapter/pi/pi-adapter.js +10 -0
- package/dist/adapter/pi/pi-transport.js +4 -0
- package/dist/adapter/pi/pi-types.js +0 -0
- package/dist/adapter/pi/usage-parser.js +1 -0
- package/dist/adapter/qwen/index.js +1 -0
- package/dist/adapter/qwen/qwen-adapter.js +4 -0
- package/dist/adapter/types.js +1 -0
- package/dist/agent/index.js +1 -0
- package/dist/agent/process.js +2 -0
- package/dist/aibot/client.js +1 -0
- package/dist/aibot/index.js +1 -0
- package/dist/aibot/types.js +0 -0
- package/dist/bridge/adapter-pool.js +1 -0
- package/dist/bridge/bridge.js +10 -0
- package/dist/bridge/deferred-events.js +1 -0
- package/dist/bridge/event-queue.js +1 -0
- package/dist/bridge/index.js +1 -0
- package/dist/bridge/respawn-manager.js +1 -0
- package/dist/bridge/revoke-handler.js +1 -0
- package/dist/bridge/runtime-config.js +1 -0
- package/dist/bridge/send-controller.js +1 -0
- package/dist/bridge/session-controller.js +9 -0
- package/dist/bridge/tool-card-utils.js +1 -0
- package/dist/core/access/allowlist-gate.js +1 -0
- package/dist/core/access/allowlist-store.js +1 -0
- package/dist/core/access/index.js +1 -0
- package/dist/core/aibot/client.js +1 -0
- package/dist/core/aibot/connection-handle.js +1 -0
- package/dist/core/aibot/connection-manager.js +1 -0
- package/dist/core/aibot/event-lifecycle-types.js +0 -0
- package/dist/core/aibot/index.js +1 -0
- package/dist/core/aibot/types.js +0 -0
- package/dist/core/config/index.js +1 -0
- package/dist/core/config/paths.js +1 -0
- package/dist/core/context/channel-context-resolution.js +1 -0
- package/dist/core/context/channel-context-store.js +1 -0
- package/dist/core/context/index.js +1 -0
- package/dist/core/context/transcript-channel-context.js +1 -0
- package/dist/core/file-ops/handler.js +1 -0
- package/dist/core/file-ops/list-files.js +1 -0
- package/dist/core/file-ops/types.js +0 -0
- package/dist/core/files/create-folder.js +1 -0
- package/dist/core/files/index.js +1 -0
- package/dist/core/files/list-files.js +1 -0
- package/dist/core/files/list-handler.js +1 -0
- package/dist/core/files/types.js +0 -0
- package/dist/core/files/utils.js +1 -0
- package/dist/core/hooks/hook-signal-store.js +2 -0
- package/dist/core/hooks/index.js +1 -0
- package/dist/core/log/bridge-event-log.js +2 -0
- package/dist/core/log/conversation-log.js +3 -0
- package/dist/core/log/index.js +1 -0
- package/dist/core/log/logger.js +6 -0
- package/dist/core/log/packet-log.js +2 -0
- package/dist/core/log/rotation.js +2 -0
- package/dist/core/mcp/event-tool-executor.js +1 -0
- package/dist/core/mcp/index.js +1 -0
- package/dist/core/mcp/internal-api-server.js +1 -0
- package/dist/core/mcp/tool-schemas.js +1 -0
- package/dist/core/mcp/tools.js +1 -0
- package/dist/core/persistence/active-event-store.js +1 -0
- package/dist/core/persistence/agent-global-config-store.js +1 -0
- package/dist/core/persistence/elicitation-store.js +1 -0
- package/dist/core/persistence/event-results-store.js +1 -0
- package/dist/core/persistence/permission-store.js +1 -0
- package/dist/core/persistence/question-store.js +1 -0
- package/dist/core/persistence/session-binding-store.js +1 -0
- package/dist/core/protocol/agent-api-media.js +1 -0
- package/dist/core/protocol/attachment-file.js +1 -0
- package/dist/core/protocol/index.js +1 -0
- package/dist/core/protocol/interaction-parser.js +1 -0
- package/dist/core/protocol/message-metadata.js +2 -0
- package/dist/core/protocol/message-reference.js +2 -0
- package/dist/core/protocol/payload-parser.js +11 -0
- package/dist/core/protocol/protocol-descriptor.js +1 -0
- package/dist/core/protocol/protocol-text.js +1 -0
- package/dist/core/provider-quota/index.js +1 -0
- package/dist/core/provider-quota/kiro.js +1 -0
- package/dist/core/provider-quota/providers.js +1 -0
- package/dist/core/provider-quota/types.js +0 -0
- package/dist/core/runtime/health.js +1 -0
- package/dist/core/runtime/index.js +1 -0
- package/dist/core/runtime/pidfile.js +2 -0
- package/dist/core/runtime/spawn.js +1 -0
- package/dist/core/text-segmentation/index.js +1 -0
- package/dist/core/text-segmentation/safe-markdown-stream-segmenter.js +6 -0
- package/dist/core/transport/index.js +1 -0
- package/dist/core/transport/json-rpc.js +3 -0
- package/dist/core/upgrade/npm-upgrader.js +2 -0
- package/dist/core/upgrade/upgrade-checker.js +1 -0
- package/dist/core/util/client-version.js +1 -0
- package/dist/core/util/codex-output-policy.js +1 -0
- package/dist/core/util/event-buffer.js +1 -0
- package/dist/core/util/index.js +1 -0
- package/dist/core/util/json-file.js +2 -0
- package/dist/core/util/normalize-string.js +1 -0
- package/dist/core/util/quoted-message-stream.js +3 -0
- package/dist/grix.js +28 -0
- package/dist/index.js +1 -0
- package/dist/log.js +3 -0
- package/dist/main.js +31 -0
- package/dist/manager.js +1 -0
- package/dist/mcp/acp-mcp-server.js +5 -0
- package/dist/mcp/stdio/server.js +10 -0
- package/dist/mcp/stream-http/config.js +1 -0
- package/dist/mcp/stream-http/connection-binding.js +1 -0
- package/dist/mcp/stream-http/event-tool-executor.js +1 -0
- package/dist/mcp/stream-http/gateway.js +1 -0
- package/dist/mcp/stream-http/index.js +1 -0
- package/dist/mcp/stream-http/security.js +1 -0
- package/dist/mcp/stream-http/session-manager.js +1 -0
- package/dist/mcp/stream-http/tool-executor.js +1 -0
- package/dist/mcp/stream-http/tool-registry.js +1 -0
- package/dist/mcp/stream-http/tool-schemas.js +1 -0
- package/dist/protocol/acp-client.js +1 -0
- package/dist/protocol/event-mapper.js +5 -0
- package/dist/protocol/index.js +1 -0
- package/dist/runtime/daemon-lock.js +2 -0
- package/dist/runtime/service-state.js +2 -0
- package/dist/scripts/approve-plan-hook.js +2 -0
- package/dist/scripts/elicitation-hook.js +6 -0
- package/dist/scripts/lib/read-stdin.js +1 -0
- package/dist/scripts/lifecycle-hook.js +2 -0
- package/dist/scripts/notification-hook.js +4 -0
- package/dist/scripts/permission-hook.js +5 -0
- package/dist/scripts/status-line-forwarder.js +2 -0
- package/dist/scripts/user-prompt-submit-hook.js +2 -0
- package/dist/service/platform-adapter.js +45 -0
- package/dist/service/process-control.js +1 -0
- package/dist/service/service-install-store.js +1 -0
- package/dist/service/service-manager.js +1 -0
- package/dist/service/service-paths.js +1 -0
- package/dist/session/index.js +1 -0
- package/dist/session/manager.js +1 -0
- package/dist/transport/index.js +1 -0
- package/dist/transport/json-rpc.js +3 -0
- package/dist/types/events.js +1 -0
- package/dist/types/index.js +1 -0
- package/dist/types/protocol.js +0 -0
- package/dist/types/session-state.js +0 -0
- package/dist/types/usage.js +0 -0
- package/openclaw-plugin/index.js +11271 -0
- package/openclaw-plugin/skills/grix-admin/SKILL.md +202 -0
- package/openclaw-plugin/skills/grix-admin/references/api-contract.md +210 -0
- package/openclaw-plugin/skills/grix-egg/SKILL.md +81 -0
- package/openclaw-plugin/skills/grix-egg/references/api-contract.md +40 -0
- package/openclaw-plugin/skills/grix-group/SKILL.md +164 -0
- package/openclaw-plugin/skills/grix-group/references/api-contract.md +97 -0
- package/openclaw-plugin/skills/grix-query/SKILL.md +247 -0
- package/openclaw-plugin/skills/grix-register/SKILL.md +86 -0
- package/openclaw-plugin/skills/grix-register/references/api-contract.md +76 -0
- package/openclaw-plugin/skills/grix-register/references/grix-concepts.md +26 -0
- package/openclaw-plugin/skills/grix-register/references/handoff-contract.md +24 -0
- package/openclaw-plugin/skills/grix-register/references/openclaw-setup.md +6 -0
- package/openclaw-plugin/skills/grix-register/references/user-replies.md +25 -0
- package/openclaw-plugin/skills/grix-register/scripts/grix_auth.ts +599 -0
- package/openclaw-plugin/skills/grix-update/SKILL.md +310 -0
- package/openclaw-plugin/skills/grix-update/references/cron-setup.md +56 -0
- package/openclaw-plugin/skills/grix-update/references/update-contract.md +149 -0
- package/openclaw-plugin/skills/message-send/SKILL.md +197 -0
- package/openclaw-plugin/skills/message-unsend/SKILL.md +186 -0
- package/openclaw-plugin/skills/message-unsend/flowchart.mermaid +27 -0
- package/openclaw-plugin/skills/openclaw-memory-setup/SKILL.md +282 -0
- package/openclaw-plugin/skills/openclaw-memory-setup/references/case-study-macpro.md +52 -0
- package/openclaw-plugin/skills/openclaw-memory-setup/references/host-readiness.md +147 -0
- package/openclaw-plugin/skills/openclaw-memory-setup/scripts/bench_ollama_embeddings.ts +326 -0
- package/openclaw-plugin/skills/openclaw-memory-setup/scripts/set_openclaw_memory_model.ts +385 -0
- package/openclaw-plugin/skills/openclaw-memory-setup/scripts/survey_host_readiness.ts +294 -0
- package/openclaw.plugin.json +24 -0
- package/package.json +114 -0
- package/scripts/install-guardian.mjs +30 -0
- package/scripts/install-guardian.sh +30 -0
- package/scripts/upgrade-guardian.sh +98 -0
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: grix-admin
|
|
3
|
+
description: 负责 OpenClaw 本地配置、绑定与运行态收口;可通过当前 agent 的 WS 通道创建新的远端 API agent,并支持查询、创建、修改 agent 分类和给 agent 挂分类,供创建和管理 agent 流程复用。
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Grix Agent Admin
|
|
7
|
+
|
|
8
|
+
`grix-admin` 负责三件事:
|
|
9
|
+
|
|
10
|
+
1. 把已有远端 agent 参数落到本地 OpenClaw,并负责绑定后的运行态收口。
|
|
11
|
+
2. 当当前主 agent 已经在线、并且具备对应 scope 时,通过 `grix_admin` 的直连动作创建新的远端 API agent,再继续本地落地。
|
|
12
|
+
3. 在创建 agent 或后续管理 agent 时,复用 `grix_admin` 的直连动作查询分类、创建分类、修改分类、给 agent 挂分类。
|
|
13
|
+
|
|
14
|
+
## 进入方式
|
|
15
|
+
|
|
16
|
+
1. 大多数情况下,从 `grix_admin` 的 `task` 入口进入本技能;`task` 第一行必须明确写出 `bind-local`、`create-and-bind` 或 `category-manage`。
|
|
17
|
+
2. 只有在本技能内部执行“远端 API agent 创建 / 分类查询 / 分类创建 / 分类修改 / 分类挂载”这些远端步骤时,才直接调用一次 `grix_admin`,并且不要再传 `task`。
|
|
18
|
+
3. 新流程里,直接调用 `grix_admin` 时一律显式传 `action`:
|
|
19
|
+
- `create_agent`
|
|
20
|
+
- `list_categories`
|
|
21
|
+
- `create_category`
|
|
22
|
+
- `update_category`
|
|
23
|
+
- `assign_category`
|
|
24
|
+
4. `create_agent` 的旧直连写法(只传 `agentName` 等字段,不传 `action`)仍可兼容,但新流程不要再使用。
|
|
25
|
+
|
|
26
|
+
## 直连动作清单
|
|
27
|
+
|
|
28
|
+
1. `action=create_agent`
|
|
29
|
+
- 必填:`agentName`
|
|
30
|
+
- 可选:`introduction`、`isMain`、`categoryId`、`categoryName`、`parentCategoryId`、`categorySortOrder`
|
|
31
|
+
- `categoryId` 和 `categoryName` 不能同时提供
|
|
32
|
+
- 给 `categoryName` 时,会先按 `parentCategoryId` 查重;找不到再创建并挂载
|
|
33
|
+
2. `action=list_categories`
|
|
34
|
+
- 必填:(无)
|
|
35
|
+
3. `action=create_category`
|
|
36
|
+
- 必填:`name`、`parentId`
|
|
37
|
+
- 可选:`sortOrder`
|
|
38
|
+
4. `action=update_category`
|
|
39
|
+
- 必填:`categoryId`、`name`、`parentId`
|
|
40
|
+
- 可选:`sortOrder`
|
|
41
|
+
5. `action=assign_category`
|
|
42
|
+
- 必填:`agentId`、`categoryId`
|
|
43
|
+
- `categoryId=0` 表示清空分类
|
|
44
|
+
|
|
45
|
+
## Mode A: bind-local(来自 grix-register 的首次交接)
|
|
46
|
+
|
|
47
|
+
输入字段(写在 `grix_admin.task` 里,且全必填):
|
|
48
|
+
|
|
49
|
+
1. 第一行固定写 `bind-local`
|
|
50
|
+
2. `agent_name`
|
|
51
|
+
3. `agent_id`
|
|
52
|
+
4. `api_endpoint`
|
|
53
|
+
5. `api_key`
|
|
54
|
+
|
|
55
|
+
执行规则:
|
|
56
|
+
|
|
57
|
+
1. 不做远端创建,直接执行本地绑定,不要调用任何会直接改 `openclaw.json` 的脚本。
|
|
58
|
+
2. 先准备本地目录:
|
|
59
|
+
- `workspace=~/.openclaw/workspace-<agent_name>`
|
|
60
|
+
- `agentDir=~/.openclaw/agents/<agent_name>/agent`
|
|
61
|
+
- persona 文件只放 `workspace` 根目录:`IDENTITY.md`、`SOUL.md`、`AGENTS.md`,以及可选的 `USER.md` / `MEMORY.md`
|
|
62
|
+
- 不要把 persona 文件放进 `agentDir`;`agentDir` 是 OpenClaw 管理的每个 agent 运行状态目录
|
|
63
|
+
- 如果 `workspace` 里缺少必要 persona 文件,补最小文件,避免新 agent 工作区为空
|
|
64
|
+
3. 读取现有配置;若路径不存在,按空对象 / 空数组处理:
|
|
65
|
+
- `channels.grix.accounts`
|
|
66
|
+
- `agents.list`
|
|
67
|
+
- `tools.profile`
|
|
68
|
+
- `tools.alsoAllow`
|
|
69
|
+
- `tools.sessions.visibility`
|
|
70
|
+
- 如需确认已有 Grix 绑定,额外用 `openclaw agents bindings --agent <agent_name> --json` 查看当前绑定列表
|
|
71
|
+
4. 计算本次目标值:
|
|
72
|
+
- `channels.grix.accounts.<agent_name>`:写入 `name`、`enabled=true`、`apiKey`、`wsUrl`、`agentId`
|
|
73
|
+
- `agents.list`:确保存在 `id=<agent_name>`、`name=<agent_name>`、`workspace`、`agentDir`、`model`
|
|
74
|
+
- Grix 绑定:确保目标 agent 最终绑定到 `grix:<agent_name>`
|
|
75
|
+
- `tools.profile`:设为 `"coding"`
|
|
76
|
+
- `tools.alsoAllow`:至少包含 `message`、`grix_query`、`grix_group`、`grix_register`、`grix_message_send`、`grix_message_unsend`
|
|
77
|
+
- 如果当前绑定目标就是主 agent,还要确保该 agent 自己的 `tools.alsoAllow` 保留 `grix_admin`、`grix_egg`、`grix_update`、`openclaw_memory_setup`;这组只放 agent 级别,不要写进全局 `tools.alsoAllow`
|
|
78
|
+
- `tools.sessions.visibility`:设为 `"agent"`
|
|
79
|
+
- 如果 `channels.grix.enabled=false`,改回 `true`
|
|
80
|
+
5. `model` 的确定规则:
|
|
81
|
+
- 先复用该本地 agent 现有条目的 `model`
|
|
82
|
+
- 如果现有条目没有,再用 `agents.defaults.model.primary`
|
|
83
|
+
- 如果仍然拿不到,明确说明缺少 model,停止执行,不要瞎猜
|
|
84
|
+
6. 用官方 CLI 逐项写入,不要整份覆盖配置:
|
|
85
|
+
- `openclaw config set channels.grix.accounts.<agent_name> '<ACCOUNT_JSON>' --strict-json`
|
|
86
|
+
- `openclaw config set agents.list '<NEXT_AGENTS_LIST_JSON>' --strict-json`
|
|
87
|
+
- `openclaw agents bind --agent <agent_name> --bind grix:<agent_name>`
|
|
88
|
+
- `openclaw config set tools.profile '"coding"' --strict-json`
|
|
89
|
+
- `openclaw config set tools.alsoAllow '["message","grix_query","grix_group","grix_register","grix_message_send","grix_message_unsend"]' --strict-json`
|
|
90
|
+
- 如果当前绑定目标就是主 agent,还要把 `grix_admin`、`grix_egg`、`grix_update`、`openclaw_memory_setup` 合并进该 agent 自己那条 `agents.list` 记录里的 `tools.alsoAllow`;不要把这组写进全局 `tools.alsoAllow`
|
|
91
|
+
- `openclaw config set tools.sessions.visibility '"agent"' --strict-json`
|
|
92
|
+
- 仅当当前配置明确把 `channels.grix.enabled` 关掉时,再执行 `openclaw config set channels.grix.enabled true --strict-json`
|
|
93
|
+
7. 写完后必须执行静态校验:
|
|
94
|
+
- `openclaw config validate`
|
|
95
|
+
- `openclaw config get --json channels.grix.accounts.<agent_name>`
|
|
96
|
+
- `openclaw config get --json agents.list`
|
|
97
|
+
- `openclaw agents bindings --agent <agent_name> --json`
|
|
98
|
+
8. 如果当前这次调用已经具备真实验证条件,必须立刻做一次真实路由验证;优先复用当前安装/验收上下文,不要新造 probe。以下情况都视为绑定后运行态仍未切换成功:
|
|
99
|
+
- 回复落到主 agent
|
|
100
|
+
- 回复表现成默认助手
|
|
101
|
+
- 回复仍是旧人格、旧配置或明显串号
|
|
102
|
+
9. 只有在当前这次 `bind-local` 调用自己承担真实验证、并且第 7 步静态校验已通过但第 8 步真实路由验证仍失败时,才允许执行一次 `openclaw gateway restart` 做定向补救;重启后必须重做同一轮真实路由验证。
|
|
103
|
+
10. 成功定义:
|
|
104
|
+
- 能做真实验证:只有“静态校验通过 + 真实路由验证通过”才算 `bind-local` 完成
|
|
105
|
+
- 当前这次调用还无法做真实验证:只能明确说明“配置已写入,运行态尚未实测,需由后续上层流程继续验证”,不要宣称已经完全生效
|
|
106
|
+
|
|
107
|
+
## Mode B: create-and-bind(已有主通道与 scope 时的后续管理)
|
|
108
|
+
|
|
109
|
+
写在 `grix_admin.task` 里的字段:
|
|
110
|
+
|
|
111
|
+
1. 第一行固定写 `create-and-bind`
|
|
112
|
+
2. `agentName`(必填)
|
|
113
|
+
3. `introduction`(可选)
|
|
114
|
+
5. `isMain`(可选,默认 `false`)
|
|
115
|
+
6. `categoryId`(可选):把新 agent 直接挂到现有分类
|
|
116
|
+
7. `categoryName`(可选):如果不存在就创建后再挂载
|
|
117
|
+
8. `parentCategoryId`(可选):只在 `categoryName` 方案里使用,默认 `0`
|
|
118
|
+
9. `categorySortOrder`(可选):只在创建分类时使用
|
|
119
|
+
|
|
120
|
+
执行规则:
|
|
121
|
+
|
|
122
|
+
1. 确认当前会话绑定了有效的 Grix 账号;禁止跨账号执行。
|
|
123
|
+
2. 如果同时给了 `categoryId` 和 `categoryName`,直接报错并停止,避免歧义。
|
|
124
|
+
3. 通过 `grix_admin` 只调用一次 `action=create_agent`,把远端创建和可选分类都交给它处理;调用时传:
|
|
125
|
+
- `action=create_agent`
|
|
126
|
+
- `agentName`
|
|
127
|
+
- 可选 `introduction`
|
|
128
|
+
- 可选 `isMain`
|
|
129
|
+
- 可选 `categoryId`
|
|
130
|
+
- 可选 `categoryName`
|
|
131
|
+
- 可选 `parentCategoryId`
|
|
132
|
+
- 可选 `categorySortOrder`
|
|
133
|
+
4. 远端创建成功后,读取返回结果里的 `createdAgent.id`、`createdAgent.agent_name`、`createdAgent.api_endpoint`、`createdAgent.api_key`。
|
|
134
|
+
5. 如果请求里带了分类信息,检查返回里是否已经带上分类挂载结果;若没有,再按 `categoryId` / `categoryName` 规则继续补做并说明原因。
|
|
135
|
+
6. `categoryName` 流程里,若同一父分类下出现多个完全同名分类,停止并要求 owner 先整理分类或改用明确的 `categoryId`。
|
|
136
|
+
7. 远端创建和可选的分类阶段成功后,再立刻转入 `bind-local` 的本地绑定步骤;如果当前这次调用已经具备真实验证上下文,就沿用同一套“静态校验 -> 真实路由验证 -> 必要时一次 restart -> 同轮复测”的收口规则,否则明确把“继续做真实验证”的责任交回上层流程。
|
|
137
|
+
8. `isMain=true` 只在确实要创建新的主 API agent 时使用;一般后续新增 agent 默认不打开。
|
|
138
|
+
9. 整个 `create-and-bind` 流程里不要把“配置写入成功”直接当成完成;只有在当前这次调用自己承担真实路由验证且验证失败后,才把一次 `openclaw gateway restart` 当成定向补救。
|
|
139
|
+
|
|
140
|
+
## Mode C: category-manage(分类管理)
|
|
141
|
+
|
|
142
|
+
写在 `grix_admin.task` 里的字段:
|
|
143
|
+
|
|
144
|
+
1. 第一行固定写 `category-manage`
|
|
145
|
+
2. `operation`(必填):只允许 `list`、`create`、`update`、`assign`
|
|
146
|
+
4. `name`(`create` / `update` 必填)
|
|
147
|
+
5. `parentId`(`create` / `update` 必填)
|
|
148
|
+
6. `sortOrder`(`create` / `update` 可选)
|
|
149
|
+
7. `categoryId`(`update` / `assign` 必填;`assign` 时允许 `0` 表示清空)
|
|
150
|
+
8. `agentId`(`assign` 必填)
|
|
151
|
+
|
|
152
|
+
执行规则:
|
|
153
|
+
|
|
154
|
+
1. 严格绑定当前会话账号,禁止跨账号执行。
|
|
155
|
+
2. 全部远端步骤只允许通过 `grix_admin` 的直连动作完成,禁止手写 HTTP、禁止临时脚本。
|
|
156
|
+
3. `operation=list`
|
|
157
|
+
- 调用 `action=list_categories`
|
|
158
|
+
4. `operation=create`
|
|
159
|
+
- 调用 `action=create_category`
|
|
160
|
+
5. `operation=update`
|
|
161
|
+
- 调用 `action=update_category`
|
|
162
|
+
6. `operation=assign`
|
|
163
|
+
- 调用 `action=assign_category`
|
|
164
|
+
- `categoryId=0` 明确表示清空该 agent 当前分类
|
|
165
|
+
7. 如果当前管理任务同时还包含“创建新 agent”,优先使用 `create-and-bind`,不要把远端创建拆散成别的自定义流程。
|
|
166
|
+
|
|
167
|
+
## 远端创建回退条件
|
|
168
|
+
|
|
169
|
+
如果当前任务既没有现成的 `agent_name`、`agent_id`、`api_endpoint`、`api_key`,又没有可用的在线主通道或 `agent.api.create` 权限,先停止本技能,明确提示用户通过 backend admin 路径创建远端 agent。拿到这些参数后,再按 `bind-local` 执行。
|
|
170
|
+
|
|
171
|
+
## Guardrails(三种模式都适用)
|
|
172
|
+
|
|
173
|
+
1. Never ask user for website account/password.
|
|
174
|
+
2. `bind-local` 模式禁止再次回调 `grix-register`,避免循环路由。
|
|
175
|
+
3. 所有远端创建 / 分类相关动作都只允许通过 `grix_admin` 直连动作走当前账号的 WS 通道;不要手写 HTTP,不要回退到旧脚本。
|
|
176
|
+
4. 完整 `api_key` 仅一次性回传,不要重复明文回显。
|
|
177
|
+
5. 本地 `openclaw config set` / `validate` 没成功前,不得宣称配置完成;当前这次调用能做真实验证时,在真实路由验证通过前也不得宣称 `bind-local` / `create-and-bind` 已完成。
|
|
178
|
+
6. 安装私聊进行中时,禁止手工修改 `openclaw.json` 后再执行 `openclaw gateway restart`。
|
|
179
|
+
7. 不要再引用或调用 `grix_agent_bind.py`;这个技能只走 OpenClaw 官方配置命令。
|
|
180
|
+
|
|
181
|
+
## Error Handling Rules
|
|
182
|
+
|
|
183
|
+
1. `bind-local` 缺少字段:明确指出缺哪个字段并停止。
|
|
184
|
+
2. `create-and-bind` 缺少 `agentName`:明确指出缺哪个字段并停止。
|
|
185
|
+
3. `create-and-bind` 若同时传了 `categoryId` 和 `categoryName`:明确指出冲突并停止。
|
|
186
|
+
4. `category-manage` 缺少 `operation` 或操作对应字段:明确指出缺哪个字段并停止。
|
|
187
|
+
5. 远端返回 `code=4003` 或报文里明确提到 `agent.api.create`:告诉 owner 去 Agent 权限页授予 `agent.api.create`。
|
|
188
|
+
6. 远端返回 `code=4003` 或报文里明确提到 `agent.category.list` / `agent.category.create` / `agent.category.update` / `agent.category.assign`:告诉 owner 去 Agent 权限页授予对应 scope。
|
|
189
|
+
7. 缺少远端 agent 参数且当前账号也不能创建:明确要求先完成 backend admin 创建。
|
|
190
|
+
8. 本地配置失败:返回失败命令与结果并停止;重点说明是哪一步 `get` / `set` / `validate` 失败。
|
|
191
|
+
9. 当前这次调用承担真实路由验证,且一次 `openclaw gateway restart` 复测后仍失败:明确说明“静态配置已写入,但运行态仍未切换成功”,按失败或部分完成收口,不要写成成功。
|
|
192
|
+
|
|
193
|
+
## Response Style
|
|
194
|
+
|
|
195
|
+
1. 明确写出当前执行的是 `bind-local`、`create-and-bind` 还是 `category-manage`。
|
|
196
|
+
2. 分阶段汇报:远端创建 / 分类处理(如有)/ 本地配置写入 / 校验结果。
|
|
197
|
+
3. 明确说明本地是否已生效;如果只有静态配置成功但当前这次调用无法做真实验证,只能写“配置已写入,运行态尚未实测,需由后续流程继续验证”;如果只是分类步骤成功,也要单独写清楚。
|
|
198
|
+
4. 如果远端创建成功但分类或本地绑定后续失败,要明确说明是“部分完成”,不要笼统写成成功。
|
|
199
|
+
|
|
200
|
+
## References
|
|
201
|
+
|
|
202
|
+
1. [references/api-contract.md](references/api-contract.md)
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
# API Contract
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
`grix-admin` 负责本地绑定与运行态收口,并在当前 agent 已具备对应 scope 时,支持通过 `grix_admin` 走 WS 完成:
|
|
6
|
+
|
|
7
|
+
1. 创建新的远端 API agent
|
|
8
|
+
2. 查询当前账号下的 agent 分类
|
|
9
|
+
3. 创建分类
|
|
10
|
+
4. 修改分类
|
|
11
|
+
5. 给指定 agent 挂分类或清空分类
|
|
12
|
+
|
|
13
|
+
## Base Rules
|
|
14
|
+
|
|
15
|
+
1. Do not ask users to provide website account/password for this flow.
|
|
16
|
+
2. 所有远端创建和分类动作都必须通过 `grix_admin` 走当前账号已认证的 WS 通道。
|
|
17
|
+
3. 如果 `agent_name` / `agent_id` / `api_endpoint` / `api_key` 不完整,且当前账号也不能远端创建,先停止并要求 backend admin 先补全。
|
|
18
|
+
4. 当前 agent 必须先在前端权限页勾选对应 scope;没有 scope 时,WS 会直接失败。
|
|
19
|
+
5. 对 `bind-local` / `create-and-bind` 而言,“配置写入成功”不等于完成;当前这次调用如果已经具备真实路由验证条件,必须把真实验证通过也算进成功条件;否则要明确把后续验证责任交回上层流程。
|
|
20
|
+
|
|
21
|
+
## Direct `grix_admin` Contract
|
|
22
|
+
|
|
23
|
+
### 1. Create Remote Agent
|
|
24
|
+
|
|
25
|
+
```json
|
|
26
|
+
{
|
|
27
|
+
"action": "create_agent",
|
|
28
|
+
"agentName": "ops helper",
|
|
29
|
+
"introduction": "负责发布和值班协作",
|
|
30
|
+
"isMain": false,
|
|
31
|
+
"categoryName": "项目助理",
|
|
32
|
+
"parentCategoryId": "0",
|
|
33
|
+
"categorySortOrder": 10
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
返回里重点读取:
|
|
38
|
+
|
|
39
|
+
1. `createdAgent.id`
|
|
40
|
+
2. `createdAgent.agent_name`
|
|
41
|
+
3. `createdAgent.api_endpoint`
|
|
42
|
+
4. `createdAgent.api_key`
|
|
43
|
+
|
|
44
|
+
需要的 scope:
|
|
45
|
+
|
|
46
|
+
1. `agent.api.create`
|
|
47
|
+
2. 如果带 `categoryName`,还可能继续需要 `agent.category.list`、`agent.category.create`、`agent.category.assign`
|
|
48
|
+
3. 如果带 `categoryId`,还需要 `agent.category.assign`
|
|
49
|
+
|
|
50
|
+
### 2. List Categories
|
|
51
|
+
|
|
52
|
+
```json
|
|
53
|
+
{
|
|
54
|
+
"action": "list_categories"
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
需要的 scope:
|
|
59
|
+
|
|
60
|
+
1. `agent.category.list`
|
|
61
|
+
|
|
62
|
+
### 3. Create Category
|
|
63
|
+
|
|
64
|
+
```json
|
|
65
|
+
{
|
|
66
|
+
"action": "create_category",
|
|
67
|
+
"name": "项目助理",
|
|
68
|
+
"parentId": "0",
|
|
69
|
+
"sortOrder": 10
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
需要的 scope:
|
|
74
|
+
|
|
75
|
+
1. `agent.category.create`
|
|
76
|
+
|
|
77
|
+
### 4. Update Category
|
|
78
|
+
|
|
79
|
+
```json
|
|
80
|
+
{
|
|
81
|
+
"action": "update_category",
|
|
82
|
+
"categoryId": "20001",
|
|
83
|
+
"name": "值班助理",
|
|
84
|
+
"parentId": "0",
|
|
85
|
+
"sortOrder": 20
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
需要的 scope:
|
|
90
|
+
|
|
91
|
+
1. `agent.category.update`
|
|
92
|
+
|
|
93
|
+
### 5. Assign or Clear Category
|
|
94
|
+
|
|
95
|
+
```json
|
|
96
|
+
{
|
|
97
|
+
"action": "assign_category",
|
|
98
|
+
"agentId": "10001",
|
|
99
|
+
"categoryId": "20001"
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
清空分类:
|
|
104
|
+
|
|
105
|
+
```json
|
|
106
|
+
{
|
|
107
|
+
"action": "assign_category",
|
|
108
|
+
"agentId": "10001",
|
|
109
|
+
"categoryId": "0"
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
需要的 scope:
|
|
114
|
+
|
|
115
|
+
1. `agent.category.assign`
|
|
116
|
+
|
|
117
|
+
## Local Bind Steps
|
|
118
|
+
|
|
119
|
+
当远端 agent 参数齐全后,继续通过 OpenClaw 官方 CLI 完成本地绑定:
|
|
120
|
+
|
|
121
|
+
1. 准备本地目录:
|
|
122
|
+
- `workspace=~/.openclaw/workspace-<agent_name>`
|
|
123
|
+
- `agentDir=~/.openclaw/agents/<agent_name>/agent`
|
|
124
|
+
- 缺必要 persona 文件时补最小 `IDENTITY.md`、`SOUL.md`、`AGENTS.md`
|
|
125
|
+
2. 按以下顺序解析 `model`:
|
|
126
|
+
- 该本地 agent 现有条目的 `model`
|
|
127
|
+
- `agents.defaults.model.primary`
|
|
128
|
+
- 还拿不到就明确报错并停止
|
|
129
|
+
3. 读取当前配置并合并:
|
|
130
|
+
- `channels.grix.accounts`
|
|
131
|
+
- `agents.list`
|
|
132
|
+
- `tools.profile`
|
|
133
|
+
- `tools.alsoAllow`
|
|
134
|
+
- `tools.sessions.visibility`
|
|
135
|
+
4. 用官方 CLI 写回:
|
|
136
|
+
- `channels.grix.accounts.<agent_name>`
|
|
137
|
+
- `agents.list`
|
|
138
|
+
- `openclaw agents bind --agent <agent_name> --bind grix:<agent_name>`
|
|
139
|
+
- `tools.profile`
|
|
140
|
+
- `tools.alsoAllow`
|
|
141
|
+
- `tools.sessions.visibility`
|
|
142
|
+
- 如需,恢复 `channels.grix.enabled=true`
|
|
143
|
+
5. 写完后先做静态校验:
|
|
144
|
+
- `openclaw config validate`
|
|
145
|
+
- `openclaw config get --json channels.grix.accounts.<agent_name>`
|
|
146
|
+
- `openclaw config get --json agents.list`
|
|
147
|
+
- `openclaw agents bindings --agent <agent_name> --json`
|
|
148
|
+
6. 如果当前这次调用已经具备真实验证条件,必须立刻做一次真实路由验证;复用当前安装/验收上下文即可,不要额外发明 probe。回复落到主 agent、默认助手、旧人格或旧配置,都视为失败。
|
|
149
|
+
7. 只有第 5 步静态校验通过、并且当前这次调用自己承担第 6 步真实验证但验证失败时,才允许执行一次 `openclaw gateway restart`;重启后必须重做同一轮真实路由验证。
|
|
150
|
+
8. 若当前这次调用无法做真实验证,只能说明“配置已写入,运行态尚未实测,需由后续流程继续验证”;不要写成“已经完全生效”。
|
|
151
|
+
|
|
152
|
+
## `bind-local` Input Contract
|
|
153
|
+
|
|
154
|
+
```json
|
|
155
|
+
{
|
|
156
|
+
"task": "bind-local\nagent_name=grix-main\nagent_id=2029786829095440384\napi_endpoint=wss://grix.dhf.pub/v1/agent-api/ws?agent_id=2029786829095440384\napi_key=ak_xxx\ndo_not_create_remote_agent=true"
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
这个模式优先做本地绑定;若当前这次调用具备真实验证条件,还必须把真实路由验证一并做完后,才算完整收口;否则只完成静态绑定并把后续验证责任显式交给上层流程。
|
|
161
|
+
|
|
162
|
+
## `create-and-bind` Input Contract
|
|
163
|
+
|
|
164
|
+
当主 agent 已具备可用账号和 `agent.api.create` scope 时,可通过 `grix_admin.task` 进入创建流程:
|
|
165
|
+
|
|
166
|
+
```json
|
|
167
|
+
{
|
|
168
|
+
"task": "create-and-bind\nagentName=ops helper\nintroduction=负责发布和值班协作\nisMain=false\ncategoryName=项目助理\nparentCategoryId=0\ncategorySortOrder=10"
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
这个模式里要按顺序做:
|
|
173
|
+
|
|
174
|
+
1. 优先直连一次 `action=create_agent`,并把可选的 `categoryId` / `categoryName` / `parentCategoryId` / `categorySortOrder` 一并传入
|
|
175
|
+
2. 若当前返回里已经带上分类挂载结果,直接继续
|
|
176
|
+
3. 若调用方走的是旧路径,或当前返回里没有分类挂载结果,再补走:
|
|
177
|
+
- `categoryId` -> `action=assign_category`
|
|
178
|
+
- `categoryName` -> `action=list_categories`
|
|
179
|
+
- 没找到 -> `action=create_category`
|
|
180
|
+
- 拿到分类 ID 后 -> `action=assign_category`
|
|
181
|
+
4. 最后走和 `bind-local` 相同的本地绑定与运行态收口流程
|
|
182
|
+
|
|
183
|
+
注意:
|
|
184
|
+
|
|
185
|
+
1. `categoryId` 和 `categoryName` 不能同时提供
|
|
186
|
+
2. `categoryName` 匹配时要同时考虑 `parentCategoryId`
|
|
187
|
+
3. 若远端返回缺少 `agent.api.create` 或某个 `agent.category.*` scope,要明确指出缺的就是哪个 scope
|
|
188
|
+
|
|
189
|
+
## `category-manage` Input Contract
|
|
190
|
+
|
|
191
|
+
当只是做后续分类管理时,通过 `grix_admin.task` 进入:
|
|
192
|
+
|
|
193
|
+
```json
|
|
194
|
+
{
|
|
195
|
+
"task": "category-manage\noperation=assign\nagentId=10001\ncategoryId=0"
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
映射关系:
|
|
200
|
+
|
|
201
|
+
1. `operation=list` -> `action=list_categories`
|
|
202
|
+
2. `operation=create` -> `action=create_category`
|
|
203
|
+
3. `operation=update` -> `action=update_category`
|
|
204
|
+
4. `operation=assign` -> `action=assign_category`
|
|
205
|
+
|
|
206
|
+
其中:
|
|
207
|
+
|
|
208
|
+
1. `operation=assign` 时 `categoryId=0` 表示清空分类
|
|
209
|
+
2. 任何一步都不能跨账号执行
|
|
210
|
+
3. 禁止自己手写 HTTP 或回退到旧脚本
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: grix-egg
|
|
3
|
+
description: 程序主线孵化:AI 只整理参数并调用 `grix_egg`,由程序完成创建、安装、绑定、验收与状态回传。
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Grix Egg
|
|
7
|
+
|
|
8
|
+
`grix-egg` 采用“程序为主、AI 为辅”。
|
|
9
|
+
AI 不再手工接力 create/bind/accept。
|
|
10
|
+
|
|
11
|
+
## 1. AI 角色边界
|
|
12
|
+
|
|
13
|
+
你只做三件事:
|
|
14
|
+
|
|
15
|
+
1. 把用户输入整理成标准参数
|
|
16
|
+
2. 调用 `grix_egg`
|
|
17
|
+
3. 按返回 JSON 给用户汇报结果/补参
|
|
18
|
+
|
|
19
|
+
不要手工执行下面动作:
|
|
20
|
+
|
|
21
|
+
1. 远端 agent 创建
|
|
22
|
+
2. 本地 bind
|
|
23
|
+
3. gateway 操作
|
|
24
|
+
4. 测试群验收
|
|
25
|
+
|
|
26
|
+
## 2. 标准调用入口
|
|
27
|
+
|
|
28
|
+
优先传安装卡原文:
|
|
29
|
+
|
|
30
|
+
```json
|
|
31
|
+
{ "installContext": "<raw json>" }
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
如果没有安装卡,传结构化参数:
|
|
35
|
+
|
|
36
|
+
1. `route`: `create_new` | `existing` | `existing_claude`
|
|
37
|
+
2. `agentName`
|
|
38
|
+
3. `agentIdSlug` 或 `localAgentName`
|
|
39
|
+
4. `downloadUrl`
|
|
40
|
+
5. `existingCredentials`(`existing` 必填)
|
|
41
|
+
6. 可选:`packageHash`、`soulContent`、`soulFile`、`categoryId`、`categoryName`、`isMain`、`expectedSubstring`、`statusTarget`、`resume`、`installId`
|
|
42
|
+
|
|
43
|
+
## 3. 路线行为
|
|
44
|
+
|
|
45
|
+
1. `create_new`:程序自动完成创建、安装、绑定、验收、状态回传
|
|
46
|
+
2. `existing`:程序自动完成已有凭证安装与验收
|
|
47
|
+
3. `existing_claude`:程序返回 `unsupported_route`,由独立 Claude 安装器处理
|
|
48
|
+
|
|
49
|
+
## 4. 验收规则
|
|
50
|
+
|
|
51
|
+
程序固定验收逻辑:
|
|
52
|
+
|
|
53
|
+
1. 测试群成员必须包含:主 agent、触发用户、目标 agent
|
|
54
|
+
2. probe 使用 `@<target_agent_id>` mention
|
|
55
|
+
3. 只接受 probe 之后目标 agent 的第一条非空回复
|
|
56
|
+
4. `expectedSubstring` 仅是可选增强条件
|
|
57
|
+
|
|
58
|
+
## 5. 结果汇报
|
|
59
|
+
|
|
60
|
+
当 `ok=true`:
|
|
61
|
+
|
|
62
|
+
1. 汇报 `install_id`、`route`、目标 agent、验收结果
|
|
63
|
+
2. 如有 `acceptance.reply_content` 和 `testGroup`,一起给用户
|
|
64
|
+
3. 若 `interaction_status=degraded`,说明主流程成功但回传部分失败
|
|
65
|
+
|
|
66
|
+
当 `ok=false`:
|
|
67
|
+
|
|
68
|
+
1. 直接使用 `reason`、`suggestion` 解释失败
|
|
69
|
+
2. 只补程序明确缺失的外部输入
|
|
70
|
+
3. 需要续跑时使用 `resume=true` + `installId`
|
|
71
|
+
|
|
72
|
+
## 6. 禁止项
|
|
73
|
+
|
|
74
|
+
1. 禁止 `curl`/`fetch`/`axios` 直连 `/v1/agent-api`
|
|
75
|
+
2. 禁止手工修改 `openclaw.json`
|
|
76
|
+
3. 远端动作只走 typed tools:`grix_admin`、`grix_group`、`grix_query`
|
|
77
|
+
4. 未完成验收前不得宣称安装成功
|
|
78
|
+
|
|
79
|
+
## References
|
|
80
|
+
|
|
81
|
+
1. [references/api-contract.md](references/api-contract.md)
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# API Contract
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
Keep `grix-egg` deterministic and tool-bounded:
|
|
6
|
+
AI only provides params, program executes.
|
|
7
|
+
|
|
8
|
+
## Required Paths
|
|
9
|
+
|
|
10
|
+
1. Remote create/category actions: `grix_admin`
|
|
11
|
+
2. Group lifecycle and membership actions: `grix_group`
|
|
12
|
+
3. Message/session query actions: `grix_query`
|
|
13
|
+
4. Local bind/update actions: official `openclaw` CLI (`config set`, `agents bind`, `config validate`)
|
|
14
|
+
|
|
15
|
+
## Prohibited Paths
|
|
16
|
+
|
|
17
|
+
1. Do not call `/v1/agent-api` using `curl`, `fetch`, `axios`, or ad hoc scripts.
|
|
18
|
+
2. Do not hand-edit `openclaw.json`.
|
|
19
|
+
3. Do not bypass typed tools with hidden protocol payloads.
|
|
20
|
+
|
|
21
|
+
## Delivery Rules
|
|
22
|
+
|
|
23
|
+
Program should emit these links on the install private chat:
|
|
24
|
+
|
|
25
|
+
1. Running/failed/success status card:
|
|
26
|
+
`grix://card/egg_install_status`
|
|
27
|
+
2. Acceptance group entry card:
|
|
28
|
+
`grix://card/conversation`
|
|
29
|
+
3. Final profile card on success:
|
|
30
|
+
`grix://card/user_profile`
|
|
31
|
+
|
|
32
|
+
If delivery fails, mark `interaction_status=degraded` but keep the main installation result unchanged.
|
|
33
|
+
|
|
34
|
+
## Acceptance Rules
|
|
35
|
+
|
|
36
|
+
1. Acceptance group must include:
|
|
37
|
+
main agent, trigger user, target agent.
|
|
38
|
+
2. Probe uses `@<target_agent_id>` mention.
|
|
39
|
+
3. Only the first non-empty reply after probe is valid.
|
|
40
|
+
4. `expectedSubstring` is an optional additional condition.
|