crawler4j-sdk 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,297 @@
1
+ Metadata-Version: 2.4
2
+ Name: crawler4j-sdk
3
+ Version: 0.2.0
4
+ Summary: Crawler4j module SDK and CLI for building standard crawler4j modules
5
+ Requires-Python: >=3.12
6
+ Description-Content-Type: text/markdown
7
+ Requires-Dist: crawler4j-contracts<1.0.0,>=0.2.0
8
+ Requires-Dist: aiohttp>=3.13.0
9
+ Requires-Dist: pyyaml>=6.0.3
10
+
11
+ # Crawler4j SDK
12
+
13
+ 用于构建 `crawler4j` 标准模块项目的 SDK 与 CLI。
14
+
15
+ 当前源码与开发者文档统一以 `0.2.0` 为基线。注意:模块自己的 `module.yaml.version` 可以独立演进,因此示例 ZIP 名称里的 `0.1.0` 仅表示模块版本,不代表 SDK 仍停留在 `0.1.x`。
16
+
17
+ ## 使用 CLI 的两种方式
18
+
19
+ 方式 1:安装后长期使用
20
+
21
+ ```bash
22
+ uv tool install crawler4j-sdk
23
+ crawler4j --help
24
+ ```
25
+
26
+ 方式 2:不安装,直接一次性运行
27
+
28
+ ```bash
29
+ uvx --from crawler4j-sdk crawler4j --help
30
+ ```
31
+
32
+ 如果你已经创建了 model 项目,并且项目目录里执行过 `uv sync`,也可以直接在项目内使用:
33
+
34
+ ```bash
35
+ uv run crawler4j --help
36
+ ```
37
+
38
+ ## 快速开始
39
+
40
+ ```python
41
+ from crawler4j_sdk import TaskContext, TaskResult, TaskScript
42
+
43
+
44
+ class MyTask(TaskScript):
45
+ name = "my_task"
46
+ display_name = "我的任务"
47
+ description = "这是一个示例任务"
48
+ default_config = {"timeout": 30}
49
+
50
+ async def execute(self, ctx: TaskContext) -> TaskResult:
51
+ if not ctx.page:
52
+ return TaskResult.fail(message="当前运行环境没有可用的浏览器 Page")
53
+
54
+ await ctx.page.goto("https://example.com")
55
+ timeout = ctx.get_config("timeout", 30)
56
+ ctx.logger.info(f"任务执行中,timeout={timeout}")
57
+
58
+ if ctx.tools and ctx.tools.has_tool("db.get_state"):
59
+ cursor = ctx.tools.call("db.get_state", key="demo:cursor")
60
+ ctx.logger.info(f"当前游标: {cursor}")
61
+
62
+ return TaskResult.ok(message="完成")
63
+ ```
64
+
65
+ ## 核心 API
66
+
67
+ ### 稳定契约(同 MAJOR 版本内冻结)
68
+
69
+ | 类型 | 说明 |
70
+ |:---|:---|
71
+ | `TaskScript` | 原子任务基类 |
72
+ | `TaskFlow` | 工作流编排基类 |
73
+ | `ModuleAssembler` | 标准模块根入口组装器 |
74
+ | `env_selector` / `EnvSelectorInfo` | 环境选择器声明与元信息 |
75
+ | `TaskContext` | 任务执行上下文 |
76
+ | `TaskResult` | 任务结果模型 |
77
+ | `TaskSignal` | 模块通知 ATM 的流程控制信号 |
78
+ | `TaskSignalAction` | `TaskSignal` 的动作枚举 |
79
+ | `EnvAction` | 任务结束后 ATM 对运行环境执行的动作 |
80
+ | `ToolsCapability` | Core 注入的统一工具入口 |
81
+ | `ToolSpec` | Core 工具声明元数据(`name` / `description` / `is_async`) |
82
+
83
+ ### TaskContext 常用能力
84
+
85
+ | 属性/方法 | 说明 |
86
+ |:---|:---|
87
+ | `ctx.page` | Playwright Page 对象 |
88
+ | `ctx.logger` | 日志记录器 |
89
+ | `ctx.http` | HTTP 客户端 |
90
+ | `ctx.config` | 宿主持久化后的模块/工作流配置视图 |
91
+ | `ctx.state` | 共享状态 |
92
+ | `ctx.runtime` | ATM/Debug 写入的执行态输入与元数据 |
93
+ | `ctx.run_subtask()` | 调用子任务 |
94
+ | `ctx.should_stop()` | 检查停止标志 |
95
+ | `ctx.screenshot()` | 截图 |
96
+ | `ctx.emit_signal()` | 向 ATM 发出流程控制信号 |
97
+ | `ctx.tools` | 宿主注入的统一扩展工具入口 |
98
+
99
+ 固定边界如下:
100
+
101
+ - `ctx.get_config()` / `ctx.config` 只读取宿主持久化的模块级配置和工作流级覆盖
102
+ - `workflow`、`devel_mode`、`execution_params`、`job_params`、`params`、`creation_params` 统一读取 `ctx.runtime`
103
+
104
+ `ctx.run_subtask()` 的返回语义:
105
+
106
+ - 成功且子任务返回了 `TaskResult.data` 时,直接返回该 payload
107
+ - 成功但没有 payload 时,返回 `True`
108
+ - 失败时,返回一个 `dict` 风格但布尔值为 `False` 的失败结果,至少保留 `status=failed` 以及可用的 `message` / `error`
109
+
110
+ ## Core 工具能力边界
111
+
112
+ 模块侧只应通过 `ctx.tools.call(...)` 使用宿主注入的扩展能力。
113
+
114
+ 当前内置工具名如下:
115
+
116
+ - `db.list_records`
117
+ - `db.replace_records`
118
+ - `db.acquire_lock`
119
+ - `db.release_lock`
120
+ - `db.is_locked`
121
+ - `db.get_state`
122
+ - `db.set_state`
123
+ - `db.exists_state`
124
+ - `ip_pool.pick_proxy`
125
+ - `env.set_proxy`
126
+ - `ui.declare_data_table`
127
+ - `ui.get_data_table`
128
+ - `captcha.match_slider`
129
+ - `captcha.match_click_targets`
130
+
131
+ 如果你需要发现能力,而不是直接调用固定名字,可以使用:
132
+
133
+ - `ctx.tools.has_tool(name)`
134
+ - `ctx.tools.list_tools()`
135
+
136
+ `ctx.tools.list_tools()` 返回的每一项都是 `ToolSpec`,其中:
137
+
138
+ - `name` 是工具名
139
+ - `description` 是工具描述
140
+ - `is_async` 表示这个工具是否需要 `await ctx.tools.call(...)`
141
+
142
+ 不要在模块里直接连接宿主数据库,也不要假设存在 ORM Session、原生 SQLite 连接或其他私有存储对象。
143
+
144
+ ### 常见调用示例
145
+
146
+ ```python
147
+ if ctx.tools and ctx.tools.has_tool("db.list_records"):
148
+ records = ctx.tools.call("db.list_records", dataset="orders")
149
+ ctx.tools.call("db.replace_records", dataset="orders", records=records)
150
+
151
+ if ctx.tools and ctx.tools.has_tool("env.set_proxy"):
152
+ await ctx.tools.call("env.set_proxy", env_id=ctx.env_id, proxy_value="http://127.0.0.1:8888")
153
+
154
+ if ctx.tools and ctx.tools.has_tool("captcha.match_slider"):
155
+ result = ctx.tools.call(
156
+ "captcha.match_slider",
157
+ background_image=bg_bytes,
158
+ puzzle_piece_image=piece_bytes,
159
+ )
160
+ ```
161
+
162
+ ## CLI 命令
163
+
164
+ ```bash
165
+ # 初始化模块项目:生成标准骨架、module.yaml、module_runtime.py
166
+ uvx --from crawler4j-sdk crawler4j module init my_model --repo owner/my_model
167
+
168
+ # 查看当前模块的版本、仓库、默认工作流、页面入口和数据表入口
169
+ uv run crawler4j module show
170
+
171
+ # 创建任务脚本:只写 tasks/<name>.py
172
+ uv run crawler4j task create login
173
+
174
+ # 创建工作流:写 workflows/<name>.py,并同步更新 module.yaml.workflows
175
+ uv run crawler4j workflow create sync_orders
176
+
177
+ # 创建代码型页面:写 ui/<name>.py,并设置 ui_extension.entry
178
+ uv run crawler4j page create dashboard
179
+
180
+ # 创建受控数据表:注册 core:data_table:<view_id>,并补 declare_ui 骨架
181
+ uv run crawler4j data-table create accounts
182
+
183
+ # 创建环境选择器:在 module_runtime.py 里追加 @env_selector(...) 函数
184
+ uv run crawler4j env-selector create pick_ready
185
+
186
+ # 设置默认配置模板
187
+ uv run crawler4j config set module --file defaults.yaml
188
+
189
+ # 发布前完整校验
190
+ uv run crawler4j check full
191
+
192
+ # 构建宿主可安装 ZIP
193
+ uv run crawler4j package build
194
+
195
+ # 发布本地 ZIP 到 GitHub Release
196
+ uv run crawler4j release publish --dry-run
197
+
198
+ # 通过 SDK CLI 桥接宿主能力
199
+ uv run crawler4j host devlink add /path/to/module
200
+ uv run crawler4j host install preview dist/my_model-0.1.0.zip --skip-remote-check
201
+ uv run crawler4j host upgrade check my_model
202
+ uv run crawler4j host debug config
203
+ ```
204
+
205
+ 当前命令树按“模块元素”和“生命周期动作”分组:
206
+
207
+ - `module`:初始化模块项目,或维护 `repo / version / default-workflow`
208
+ - `task`:管理 `tasks/` 里的 `TaskScript`
209
+ - `workflow`:管理 `workflows/` 和 `module.yaml.workflows`
210
+ - `page`:管理代码型页面和 `ui_extension.entry`
211
+ - `data-table`:管理受控 `core:data_table:<id>` 入口
212
+ - `env-selector`:管理 `module_runtime.py` 里的环境选择策略函数
213
+ - `config`:管理 `module.yaml.config_defaults`
214
+ - `package`:构建和校验安装 ZIP
215
+ - `release`:看本地发布状态、检查 GitHub Release 最新版本、发布 Release 资产
216
+ - `host`:通过 SDK CLI 桥接宿主的 DevLink、安装、升级和调试配置
217
+ - `check`:运行 `structure / release / full` 三档完整性校验
218
+
219
+ `module init` 会默认:
220
+
221
+ - 生成 `.gitignore`
222
+ - 生成 `.python-version`
223
+ - 生成 `module_runtime.py`
224
+ - 执行 `git init`
225
+ - 执行 `uv sync`
226
+
227
+ 如果你在 CI 或脚本里使用 CLI,可以直接补齐 `--repo`、`--no-git`、`--no-install` 等参数。第一版命令树已经切到 `module / task / workflow / page / data-table / env-selector / config / package / release / host / check` 分组体系,不再兼容旧平铺命令。
228
+
229
+ 调试主路径已经收敛到 Core 调试会话。旧的 `debug_runner.py` 辅助脚本已从宿主仓库移除,CLI 也不再生成任何本地调试壳脚本。
230
+ 模块持久配置由宿主统一维护,`config_schema.json` / `strategy.yaml` 已不再受支持;详情页扩展数据表也应通过 `data-table create` 写入受控的 `core:data_table:<view_id>` 入口,而不是手改 `module.yaml`。
231
+
232
+ ## 工作流示例
233
+
234
+ ```python
235
+ from crawler4j_sdk import TaskContext, TaskFlow
236
+
237
+
238
+ class MyWorkflow(TaskFlow):
239
+ name = "my_workflow"
240
+
241
+ async def run(self, ctx: TaskContext) -> None:
242
+ await ctx.run_subtask("login")
243
+
244
+ while not ctx.should_stop():
245
+ ctx.state["phase"] = "claim"
246
+ task = await ctx.run_subtask("claim_task")
247
+ if not task:
248
+ break
249
+
250
+ ctx.state["phase"] = "process"
251
+ await ctx.run_subtask("process", task=task)
252
+ ```
253
+
254
+ ## 运行期控制信号
255
+
256
+ 模块如果需要让 ATM 接管流程动作,例如等待人工确认或在失败后销毁环境,应通过 `TaskSignal`,而不是直接操作宿主运行环境。
257
+
258
+ ```python
259
+ from crawler4j_sdk import EnvAction, TaskResult, TaskSignal
260
+
261
+
262
+ return TaskResult.fail(
263
+ message="检测到黑号",
264
+ error="black_account",
265
+ signal=TaskSignal.fail(
266
+ message="检测到黑号",
267
+ error="black_account",
268
+ env_action=EnvAction.DESTROY,
269
+ ),
270
+ )
271
+ ```
272
+
273
+ 当前 `TaskScript` / `TaskFlow` 自身只有一个稳定入口方法:`execute(ctx)` 或 `run(ctx)`。
274
+ `module_runtime.py` 现在是标准模块文件,不再是可选扩展点。模块级生命周期统一在其中实现:
275
+
276
+ - `prepare_env`
277
+ - `declare_ui`
278
+ - `@env_selector(...)` 声明环境选择回调,供 ATM 的“选择环境”模式调用
279
+ - `init_env`
280
+ - `before_run`
281
+ - `on_success`
282
+ - `on_failure`
283
+ - `on_timeout`
284
+ - `on_cleanup`
285
+
286
+ 脚手架默认会生成两个示例环境选择器:
287
+
288
+ - `return_none`:占位选择器,固定返回 `None`,用于提醒开发者必须替换成真实逻辑
289
+ - `random_ready`:从当前 `ready` 候选环境里随机选择一个
290
+
291
+ `on_cleanup` 发生在 ATM 完成环境动作之后。如果模块需要在环境已删除后清理自己的数据,应在 `on_cleanup` 中读取 `ctx.runtime["env_action"]`,而不是再增加一套额外的“环境删除 hook”。
292
+
293
+ ## 版本兼容
294
+
295
+ - Python: `>= 3.12`
296
+ - 遵循语义化版本(SemVer)
297
+ - 当前源码包版本:`0.2.0`
@@ -0,0 +1,287 @@
1
+ # Crawler4j SDK
2
+
3
+ 用于构建 `crawler4j` 标准模块项目的 SDK 与 CLI。
4
+
5
+ 当前源码与开发者文档统一以 `0.2.0` 为基线。注意:模块自己的 `module.yaml.version` 可以独立演进,因此示例 ZIP 名称里的 `0.1.0` 仅表示模块版本,不代表 SDK 仍停留在 `0.1.x`。
6
+
7
+ ## 使用 CLI 的两种方式
8
+
9
+ 方式 1:安装后长期使用
10
+
11
+ ```bash
12
+ uv tool install crawler4j-sdk
13
+ crawler4j --help
14
+ ```
15
+
16
+ 方式 2:不安装,直接一次性运行
17
+
18
+ ```bash
19
+ uvx --from crawler4j-sdk crawler4j --help
20
+ ```
21
+
22
+ 如果你已经创建了 model 项目,并且项目目录里执行过 `uv sync`,也可以直接在项目内使用:
23
+
24
+ ```bash
25
+ uv run crawler4j --help
26
+ ```
27
+
28
+ ## 快速开始
29
+
30
+ ```python
31
+ from crawler4j_sdk import TaskContext, TaskResult, TaskScript
32
+
33
+
34
+ class MyTask(TaskScript):
35
+ name = "my_task"
36
+ display_name = "我的任务"
37
+ description = "这是一个示例任务"
38
+ default_config = {"timeout": 30}
39
+
40
+ async def execute(self, ctx: TaskContext) -> TaskResult:
41
+ if not ctx.page:
42
+ return TaskResult.fail(message="当前运行环境没有可用的浏览器 Page")
43
+
44
+ await ctx.page.goto("https://example.com")
45
+ timeout = ctx.get_config("timeout", 30)
46
+ ctx.logger.info(f"任务执行中,timeout={timeout}")
47
+
48
+ if ctx.tools and ctx.tools.has_tool("db.get_state"):
49
+ cursor = ctx.tools.call("db.get_state", key="demo:cursor")
50
+ ctx.logger.info(f"当前游标: {cursor}")
51
+
52
+ return TaskResult.ok(message="完成")
53
+ ```
54
+
55
+ ## 核心 API
56
+
57
+ ### 稳定契约(同 MAJOR 版本内冻结)
58
+
59
+ | 类型 | 说明 |
60
+ |:---|:---|
61
+ | `TaskScript` | 原子任务基类 |
62
+ | `TaskFlow` | 工作流编排基类 |
63
+ | `ModuleAssembler` | 标准模块根入口组装器 |
64
+ | `env_selector` / `EnvSelectorInfo` | 环境选择器声明与元信息 |
65
+ | `TaskContext` | 任务执行上下文 |
66
+ | `TaskResult` | 任务结果模型 |
67
+ | `TaskSignal` | 模块通知 ATM 的流程控制信号 |
68
+ | `TaskSignalAction` | `TaskSignal` 的动作枚举 |
69
+ | `EnvAction` | 任务结束后 ATM 对运行环境执行的动作 |
70
+ | `ToolsCapability` | Core 注入的统一工具入口 |
71
+ | `ToolSpec` | Core 工具声明元数据(`name` / `description` / `is_async`) |
72
+
73
+ ### TaskContext 常用能力
74
+
75
+ | 属性/方法 | 说明 |
76
+ |:---|:---|
77
+ | `ctx.page` | Playwright Page 对象 |
78
+ | `ctx.logger` | 日志记录器 |
79
+ | `ctx.http` | HTTP 客户端 |
80
+ | `ctx.config` | 宿主持久化后的模块/工作流配置视图 |
81
+ | `ctx.state` | 共享状态 |
82
+ | `ctx.runtime` | ATM/Debug 写入的执行态输入与元数据 |
83
+ | `ctx.run_subtask()` | 调用子任务 |
84
+ | `ctx.should_stop()` | 检查停止标志 |
85
+ | `ctx.screenshot()` | 截图 |
86
+ | `ctx.emit_signal()` | 向 ATM 发出流程控制信号 |
87
+ | `ctx.tools` | 宿主注入的统一扩展工具入口 |
88
+
89
+ 固定边界如下:
90
+
91
+ - `ctx.get_config()` / `ctx.config` 只读取宿主持久化的模块级配置和工作流级覆盖
92
+ - `workflow`、`devel_mode`、`execution_params`、`job_params`、`params`、`creation_params` 统一读取 `ctx.runtime`
93
+
94
+ `ctx.run_subtask()` 的返回语义:
95
+
96
+ - 成功且子任务返回了 `TaskResult.data` 时,直接返回该 payload
97
+ - 成功但没有 payload 时,返回 `True`
98
+ - 失败时,返回一个 `dict` 风格但布尔值为 `False` 的失败结果,至少保留 `status=failed` 以及可用的 `message` / `error`
99
+
100
+ ## Core 工具能力边界
101
+
102
+ 模块侧只应通过 `ctx.tools.call(...)` 使用宿主注入的扩展能力。
103
+
104
+ 当前内置工具名如下:
105
+
106
+ - `db.list_records`
107
+ - `db.replace_records`
108
+ - `db.acquire_lock`
109
+ - `db.release_lock`
110
+ - `db.is_locked`
111
+ - `db.get_state`
112
+ - `db.set_state`
113
+ - `db.exists_state`
114
+ - `ip_pool.pick_proxy`
115
+ - `env.set_proxy`
116
+ - `ui.declare_data_table`
117
+ - `ui.get_data_table`
118
+ - `captcha.match_slider`
119
+ - `captcha.match_click_targets`
120
+
121
+ 如果你需要发现能力,而不是直接调用固定名字,可以使用:
122
+
123
+ - `ctx.tools.has_tool(name)`
124
+ - `ctx.tools.list_tools()`
125
+
126
+ `ctx.tools.list_tools()` 返回的每一项都是 `ToolSpec`,其中:
127
+
128
+ - `name` 是工具名
129
+ - `description` 是工具描述
130
+ - `is_async` 表示这个工具是否需要 `await ctx.tools.call(...)`
131
+
132
+ 不要在模块里直接连接宿主数据库,也不要假设存在 ORM Session、原生 SQLite 连接或其他私有存储对象。
133
+
134
+ ### 常见调用示例
135
+
136
+ ```python
137
+ if ctx.tools and ctx.tools.has_tool("db.list_records"):
138
+ records = ctx.tools.call("db.list_records", dataset="orders")
139
+ ctx.tools.call("db.replace_records", dataset="orders", records=records)
140
+
141
+ if ctx.tools and ctx.tools.has_tool("env.set_proxy"):
142
+ await ctx.tools.call("env.set_proxy", env_id=ctx.env_id, proxy_value="http://127.0.0.1:8888")
143
+
144
+ if ctx.tools and ctx.tools.has_tool("captcha.match_slider"):
145
+ result = ctx.tools.call(
146
+ "captcha.match_slider",
147
+ background_image=bg_bytes,
148
+ puzzle_piece_image=piece_bytes,
149
+ )
150
+ ```
151
+
152
+ ## CLI 命令
153
+
154
+ ```bash
155
+ # 初始化模块项目:生成标准骨架、module.yaml、module_runtime.py
156
+ uvx --from crawler4j-sdk crawler4j module init my_model --repo owner/my_model
157
+
158
+ # 查看当前模块的版本、仓库、默认工作流、页面入口和数据表入口
159
+ uv run crawler4j module show
160
+
161
+ # 创建任务脚本:只写 tasks/<name>.py
162
+ uv run crawler4j task create login
163
+
164
+ # 创建工作流:写 workflows/<name>.py,并同步更新 module.yaml.workflows
165
+ uv run crawler4j workflow create sync_orders
166
+
167
+ # 创建代码型页面:写 ui/<name>.py,并设置 ui_extension.entry
168
+ uv run crawler4j page create dashboard
169
+
170
+ # 创建受控数据表:注册 core:data_table:<view_id>,并补 declare_ui 骨架
171
+ uv run crawler4j data-table create accounts
172
+
173
+ # 创建环境选择器:在 module_runtime.py 里追加 @env_selector(...) 函数
174
+ uv run crawler4j env-selector create pick_ready
175
+
176
+ # 设置默认配置模板
177
+ uv run crawler4j config set module --file defaults.yaml
178
+
179
+ # 发布前完整校验
180
+ uv run crawler4j check full
181
+
182
+ # 构建宿主可安装 ZIP
183
+ uv run crawler4j package build
184
+
185
+ # 发布本地 ZIP 到 GitHub Release
186
+ uv run crawler4j release publish --dry-run
187
+
188
+ # 通过 SDK CLI 桥接宿主能力
189
+ uv run crawler4j host devlink add /path/to/module
190
+ uv run crawler4j host install preview dist/my_model-0.1.0.zip --skip-remote-check
191
+ uv run crawler4j host upgrade check my_model
192
+ uv run crawler4j host debug config
193
+ ```
194
+
195
+ 当前命令树按“模块元素”和“生命周期动作”分组:
196
+
197
+ - `module`:初始化模块项目,或维护 `repo / version / default-workflow`
198
+ - `task`:管理 `tasks/` 里的 `TaskScript`
199
+ - `workflow`:管理 `workflows/` 和 `module.yaml.workflows`
200
+ - `page`:管理代码型页面和 `ui_extension.entry`
201
+ - `data-table`:管理受控 `core:data_table:<id>` 入口
202
+ - `env-selector`:管理 `module_runtime.py` 里的环境选择策略函数
203
+ - `config`:管理 `module.yaml.config_defaults`
204
+ - `package`:构建和校验安装 ZIP
205
+ - `release`:看本地发布状态、检查 GitHub Release 最新版本、发布 Release 资产
206
+ - `host`:通过 SDK CLI 桥接宿主的 DevLink、安装、升级和调试配置
207
+ - `check`:运行 `structure / release / full` 三档完整性校验
208
+
209
+ `module init` 会默认:
210
+
211
+ - 生成 `.gitignore`
212
+ - 生成 `.python-version`
213
+ - 生成 `module_runtime.py`
214
+ - 执行 `git init`
215
+ - 执行 `uv sync`
216
+
217
+ 如果你在 CI 或脚本里使用 CLI,可以直接补齐 `--repo`、`--no-git`、`--no-install` 等参数。第一版命令树已经切到 `module / task / workflow / page / data-table / env-selector / config / package / release / host / check` 分组体系,不再兼容旧平铺命令。
218
+
219
+ 调试主路径已经收敛到 Core 调试会话。旧的 `debug_runner.py` 辅助脚本已从宿主仓库移除,CLI 也不再生成任何本地调试壳脚本。
220
+ 模块持久配置由宿主统一维护,`config_schema.json` / `strategy.yaml` 已不再受支持;详情页扩展数据表也应通过 `data-table create` 写入受控的 `core:data_table:<view_id>` 入口,而不是手改 `module.yaml`。
221
+
222
+ ## 工作流示例
223
+
224
+ ```python
225
+ from crawler4j_sdk import TaskContext, TaskFlow
226
+
227
+
228
+ class MyWorkflow(TaskFlow):
229
+ name = "my_workflow"
230
+
231
+ async def run(self, ctx: TaskContext) -> None:
232
+ await ctx.run_subtask("login")
233
+
234
+ while not ctx.should_stop():
235
+ ctx.state["phase"] = "claim"
236
+ task = await ctx.run_subtask("claim_task")
237
+ if not task:
238
+ break
239
+
240
+ ctx.state["phase"] = "process"
241
+ await ctx.run_subtask("process", task=task)
242
+ ```
243
+
244
+ ## 运行期控制信号
245
+
246
+ 模块如果需要让 ATM 接管流程动作,例如等待人工确认或在失败后销毁环境,应通过 `TaskSignal`,而不是直接操作宿主运行环境。
247
+
248
+ ```python
249
+ from crawler4j_sdk import EnvAction, TaskResult, TaskSignal
250
+
251
+
252
+ return TaskResult.fail(
253
+ message="检测到黑号",
254
+ error="black_account",
255
+ signal=TaskSignal.fail(
256
+ message="检测到黑号",
257
+ error="black_account",
258
+ env_action=EnvAction.DESTROY,
259
+ ),
260
+ )
261
+ ```
262
+
263
+ 当前 `TaskScript` / `TaskFlow` 自身只有一个稳定入口方法:`execute(ctx)` 或 `run(ctx)`。
264
+ `module_runtime.py` 现在是标准模块文件,不再是可选扩展点。模块级生命周期统一在其中实现:
265
+
266
+ - `prepare_env`
267
+ - `declare_ui`
268
+ - `@env_selector(...)` 声明环境选择回调,供 ATM 的“选择环境”模式调用
269
+ - `init_env`
270
+ - `before_run`
271
+ - `on_success`
272
+ - `on_failure`
273
+ - `on_timeout`
274
+ - `on_cleanup`
275
+
276
+ 脚手架默认会生成两个示例环境选择器:
277
+
278
+ - `return_none`:占位选择器,固定返回 `None`,用于提醒开发者必须替换成真实逻辑
279
+ - `random_ready`:从当前 `ready` 候选环境里随机选择一个
280
+
281
+ `on_cleanup` 发生在 ATM 完成环境动作之后。如果模块需要在环境已删除后清理自己的数据,应在 `on_cleanup` 中读取 `ctx.runtime["env_action"]`,而不是再增加一套额外的“环境删除 hook”。
282
+
283
+ ## 版本兼容
284
+
285
+ - Python: `>= 3.12`
286
+ - 遵循语义化版本(SemVer)
287
+ - 当前源码包版本:`0.2.0`