@realqiyan/agent-task 0.0.1-rc.1 → 0.0.1-rc.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 +4 -2
- package/bin/agent-task.mjs +7 -12
- package/deploy/agent-task.service.example +2 -2
- package/dist/acp.js +7 -6
- package/dist/admin.js +3 -3
- package/dist/config.js +15 -56
- package/dist/environment.js +13 -0
- package/dist/executor-runtime.js +37 -0
- package/dist/launcher.js +238 -0
- package/dist/lock.js +16 -0
- package/dist/main.js +74 -30
- package/dist/model.js +2 -5
- package/dist/node-execution.js +2 -3
- package/dist/permissions.js +3 -4
- package/dist/private-files.js +16 -2
- package/dist/results.js +9 -15
- package/dist/settings-contract.js +1 -1
- package/dist/settings.js +25 -8
- package/dist/startup.js +56 -0
- package/dist/tasks.js +5 -6
- package/dist/web/api.js +1 -1
- package/dist/web/app.js +7 -0
- package/dist/web/settings-view.js +33 -28
- package/dist/web.js +14 -2
- package/dist/worker.js +2 -2
- package/docs/architecture-guidelines.md +2 -2
- package/docs/deployment.md +40 -38
- package/docs/design-proposal.md +2 -2
- package/docs/release.md +8 -8
- package/docs/technical-design.md +3 -3
- package/docs/validation.md +17 -9
- package/docs/web-console.md +10 -16
- package/package.json +2 -2
- package/scripts/install-lib.mjs +17 -24
- package/scripts/probe-agent.mjs +8 -3
- package/scripts/service-runner.mjs +2 -7
- package/scripts/setup-wizard.mjs +2 -2
- package/scripts/setup.mjs +24 -20
- package/scripts/uninstall.mjs +13 -9
package/dist/web.js
CHANGED
|
@@ -16,7 +16,7 @@ function query(schema, request) {
|
|
|
16
16
|
}
|
|
17
17
|
return schema.parse(value);
|
|
18
18
|
}
|
|
19
|
-
export function createWebRouter(tasks, settings, token, fatal) {
|
|
19
|
+
export function createWebRouter(tasks, settings, token, fatal, restart, instanceId) {
|
|
20
20
|
const router = express.Router(), authorized = bearerVerifier(token);
|
|
21
21
|
router.use((req, res, next) => {
|
|
22
22
|
res.set({
|
|
@@ -75,7 +75,7 @@ export function createWebRouter(tasks, settings, token, fatal) {
|
|
|
75
75
|
res.status(400).json({ error: "Request rejected; check inputs and refresh the current state" });
|
|
76
76
|
}
|
|
77
77
|
};
|
|
78
|
-
router.get("/api/status", respond(() => ({ ...tasks.status(), settings: settings.status() })));
|
|
78
|
+
router.get("/api/status", respond(() => ({ ...tasks.status(), settings: settings.status(), instanceId })));
|
|
79
79
|
router.get("/api/tasks", respond(req => {
|
|
80
80
|
const input = query(taskListSchema, req), page = tasks.list(input.after, input.limit);
|
|
81
81
|
return { ...page, items: page.items.map(task => ({ id: task.id, name: task.input.name, status: task.status, nextAt: task.nextAt, schedule: task.input.schedule })) };
|
|
@@ -96,6 +96,18 @@ export function createWebRouter(tasks, settings, token, fatal) {
|
|
|
96
96
|
}));
|
|
97
97
|
router.post("/api/settings/config", respond(req => settings.saveConfig(req.body)));
|
|
98
98
|
router.post("/api/settings/credential", respond(req => settings.saveCredential(req.body)));
|
|
99
|
+
router.post("/api/restart", async (req, res) => {
|
|
100
|
+
if (!req.body || Array.isArray(req.body) || typeof req.body !== "object" || Object.keys(req.body).length) {
|
|
101
|
+
res.status(400).json({ error: "Expected an empty request" });
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
try {
|
|
105
|
+
res.json({ data: await restart() });
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
res.status(503).json({ error: "Restart could not be confirmed; inspect private logs and use CLI status before retrying" });
|
|
109
|
+
}
|
|
110
|
+
});
|
|
99
111
|
router.use((_req, res) => { res.status(404).end(); });
|
|
100
112
|
router.use(((error, _req, res, _next) => {
|
|
101
113
|
res.status(error.status === 413 ? 413 : error.status === 415 ? 415 : 400).json({ error: "Invalid request body" });
|
package/dist/worker.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { openAcp } from "./acp.js";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { errorText, executorSchema } from "./model.js";
|
|
4
|
-
const inputSchema = z.object({ executor: executorSchema, inputFiles: z.array(z.string()).max(20) }).strict();
|
|
4
|
+
const inputSchema = z.object({ executor: executorSchema, inputFiles: z.array(z.string()).max(20), resultsRoot: z.string().min(1) }).strict();
|
|
5
5
|
const controller = new AbortController();
|
|
6
6
|
const pending = new Map();
|
|
7
7
|
let sequence = 0, started = false, busy = false;
|
|
@@ -64,7 +64,7 @@ process.on("message", (message) => {
|
|
|
64
64
|
started = true;
|
|
65
65
|
opening = (async () => {
|
|
66
66
|
const input = inputSchema.parse(message.input);
|
|
67
|
-
session = await openAcp(input.executor, controller.signal, emit, input.inputFiles);
|
|
67
|
+
session = await openAcp(input.executor, controller.signal, emit, input.resultsRoot, input.inputFiles);
|
|
68
68
|
void session.closed.then(() => { if (!closing)
|
|
69
69
|
void fail(new Error("ACP process exited")); });
|
|
70
70
|
if (!controller.signal.aborted)
|
|
@@ -33,10 +33,10 @@
|
|
|
33
33
|
|
|
34
34
|
- 执行端声明能力、管理员授权、真实验证证据分别表达,不能相互推导。
|
|
35
35
|
- 只有管理员注册的执行端可启动。命令、凭据和能力端点属于管理员;任务可选择现有绝对工作空间,但路径校验不是授权或沙箱。
|
|
36
|
-
-
|
|
36
|
+
- 授权须在可控制的实际操作入口检查。沿用执行端原生授权,额外 ACP 审批请求一律拒绝;`unattendedApproved` 不能证明所有原生工具已受控。
|
|
37
37
|
- Prompt、角色、技能、工具标题、协议成功和人工事实回复都不授予权限。取消、恢复和跨 Agent 传递不能扩大授权。
|
|
38
38
|
- 无人值守执行不等待交互审批;授权不充分或元数据不足时拒绝,不退回更宽权限或自动永久允许。
|
|
39
|
-
- MCP token、管理员 token
|
|
39
|
+
- MCP token、管理员 token、协调模型与执行端凭据分开;执行端继承用户原生环境前过滤服务私密变量,不有意记录秘密。Agent 输出和协调日志仍可能敏感。
|
|
40
40
|
- 优先使用执行端原生配置,不读取其私有业务存储格式,不将特定领域规则塞进平台。
|
|
41
41
|
|
|
42
42
|
## 存储与发布
|
package/docs/deployment.md
CHANGED
|
@@ -1,113 +1,115 @@
|
|
|
1
1
|
# 安装与运行
|
|
2
2
|
|
|
3
|
-
支持 Linux 和 macOS,要求 Node.js **22.19+** 与 npm
|
|
3
|
+
支持 Linux 和 macOS,要求 Node.js **22.19+** 与 npm。安装包包含运行产物,不需要源码或 TypeScript。发布状态和平台证据见[验证说明](validation.md),发布操作见[发布说明](release.md)。
|
|
4
4
|
|
|
5
5
|
## 安装与交互配置
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
8
|
npm install -g @realqiyan/agent-task
|
|
9
|
-
agent-task --version
|
|
10
9
|
agent-task setup
|
|
11
10
|
agent-task start
|
|
12
11
|
```
|
|
13
12
|
|
|
14
|
-
使用当前用户可写的 npm prefix,不以 root
|
|
13
|
+
使用当前用户可写的 npm prefix,不以 root 运行。若使用 nvm 等版本管理器,先选定后台长期使用的 Node;服务记录绝对路径,移除该 Node 或全局包会使它无法启动。
|
|
15
14
|
|
|
16
|
-
|
|
15
|
+
向导收集工作空间、可选执行端、可选协调模型及端口,并询问是否安装后台服务。默认只准备配置,不启动服务、不请求密钥。取消确认不写配置。无终端环境须明确提供参数:
|
|
17
16
|
|
|
18
17
|
```bash
|
|
19
18
|
agent-task setup --executors none --cwd /absolute/workspace --prepare-only
|
|
20
|
-
#
|
|
19
|
+
# 另一个首次安装选择:安装 Pi ACP 组件,但不授权执行。
|
|
21
20
|
agent-task setup --home /absolute/private/agent-task \
|
|
22
21
|
--executors pi --cwd /absolute/workspace \
|
|
23
22
|
--provider deepseek --model deepseek-flash --prepare-only
|
|
24
23
|
```
|
|
25
24
|
|
|
26
|
-
|
|
25
|
+
工作空间须预先存在,且位于安装目录及 npm 包之外。完整选项见 `agent-task setup --help`;组件版本及平台实现见 [install-lib.mjs](../scripts/install-lib.mjs)。安装不执行原生登录或批准任务。
|
|
27
26
|
|
|
28
|
-
|
|
27
|
+
默认安装目录为 Linux 的 `~/.local/share/agent-task` 或 macOS 的 `~/Library/Application Support/agent-task`。自定义 `--home` 后,后续命令也传同一路径。setup 输出私有文件路径和控制台地址,**不打印 token**。
|
|
29
28
|
|
|
30
|
-
|
|
29
|
+
## 数据目录与配置
|
|
31
30
|
|
|
32
|
-
|
|
31
|
+
初始 `dataDir` 是安装目录下的 `data/`。运行配置、私有环境文件、任务和协调会话记录、结果及服务文件日志均位于当前 `dataDir`。配置契约见 [config.ts](../src/config.ts),路径选择与新目录准备见 [startup.ts](../src/startup.ts)。
|
|
33
32
|
|
|
34
|
-
|
|
35
|
-
2. 在[控制台](web-console.md)配置协调模型、单独保存模型密钥及注册 ACP 执行端。未配置协调模型时,服务仍可启动和配置,但不能接收可执行任务。
|
|
36
|
-
3. 如需安装器提供的执行端,用 setup 输出的专属路径按原生方式认证:[Pi](https://github.com/earendil-works/pi/tree/v1.3.0/packages/coding-agent/docs)、[Codex ACP](https://github.com/agentclientprotocol/codex-acp#readme)、[OpenCode](https://opencode.ai/docs/acp/)。后台服务与原生登录须使用一致的 HOME/配置目录。
|
|
37
|
-
4. 停服后审核执行端原生工具、沙箱、凭据传递及结果读取范围,再由管理员明确批准 `unattendedApproved`。安装器从不登录、自动批准或扩大权限;ACP allowlist 不是完整工具授权或 OS 沙箱。
|
|
38
|
-
5. 保存后手动重启。正在运行或等待人工回复的任务可能持有 ACP 会话,重启前须核查;不保证无损续接。
|
|
33
|
+
安装目录保留固定的 `launch.json` 定位文件、安装清单、组件及启动器控制资源;它不是另一份运行配置。这个固定入口让原有 CLI、systemd 和 launchd 注册在更换数据目录后仍能找到服务。npm 包、用户工作空间和执行端原生配置不随 `dataDir` 移动。
|
|
39
34
|
|
|
40
|
-
|
|
35
|
+
在 Web 修改数据目录后,下次启动只在目标目录准备已保存的配置和私有环境,并切换定位文件;**不复制任务、会话、结果或日志,不删除旧目录**。目标已有不同启动文件时拒绝覆盖;须停服核对并显式准备目标,不自动合并或修复。新目录没有旧任务,不能把切换目录当成故障恢复或重放许可。
|
|
41
36
|
|
|
42
|
-
|
|
37
|
+
1. 运行 `agent-task start`,打开输出的本机控制台地址。用本地编辑器读取当前 `dataDir/environment.json` 的 `AGENT_TASK_ADMIN_TOKEN`,在连接表单输入。不要把 token 放进 URL、命令行、Git、截图或日志。
|
|
38
|
+
2. 在[控制台](web-console.md)配置协调模型、单独保存模型密钥及注册执行端。未配置协调模型时可管理服务,但不能接收可执行任务。
|
|
39
|
+
3. 按执行端官方方式准备原生认证及权限:[Pi](https://github.com/earendil-works/pi/tree/v1.3.0/packages/coding-agent/docs)、[Codex ACP](https://github.com/agentclientprotocol/codex-acp#readme)、[Claude ACP](https://github.com/zed-industries/claude-agent-acp)、[OpenCode](https://opencode.ai/docs/acp/)。后台服务与原生登录须使用同一 OS 用户及相应 HOME/配置目录。
|
|
40
|
+
4. 审核原生工具、沙箱、凭据和结果文件读取权限后,再明确批准无人值守执行。注册只选类型,不配置命令、参数、环境变量清单或 ACP 工具白名单;系统映射见 [executor-runtime.ts](../src/executor-runtime.ts)。额外 ACP 审批请求被拒绝,不自动批准。
|
|
41
|
+
|
|
42
|
+
服务和协调员的秘密只从私有环境文件加载,不回退到 shell 模型密钥。执行端沿用启动上下文中的用户原生环境,但排除私有文件变量、服务/管理员令牌、协调员凭据及同值别名。不要把执行端原生凭据放入服务的私有环境文件。后台管理器的环境未必等于交互式 shell;启动器不执行 shell 启动脚本,也不导入原生认证文件。隔离实现见 [environment.ts](../src/environment.ts)、[executor-runtime.ts](../src/executor-runtime.ts)。这不是同一 OS 用户下的沙箱。
|
|
43
|
+
|
|
44
|
+
显式握手检查:
|
|
43
45
|
|
|
44
46
|
```bash
|
|
45
47
|
agent-task setup --check --probe
|
|
46
48
|
```
|
|
47
49
|
|
|
48
|
-
|
|
50
|
+
这只做 ACP initialize,不验证模型认证、任务成功或原生工具权限。普通 Pi CLI 不是 ACP server,须配合 pi-acp。自行安装的受支持适配器也可使用,只要在服务的原生 PATH 中;无需填写绝对可执行文件路径。
|
|
49
51
|
|
|
50
|
-
##
|
|
52
|
+
## 前台、重启与健康检查
|
|
51
53
|
|
|
52
54
|
```bash
|
|
53
55
|
agent-task start
|
|
54
|
-
#
|
|
56
|
+
# 另一个终端:
|
|
55
57
|
agent-task status
|
|
58
|
+
agent-task restart
|
|
56
59
|
```
|
|
57
60
|
|
|
58
|
-
Ctrl-C
|
|
61
|
+
Ctrl-C 关闭前台启动器及其运行进程。`restart` 和 Web 的“重启并检查可用性”使用同一启动器:先停止新派发、关闭活动执行并释放数据所有权,再用已保存配置创建新进程。前台终端和后台服务管理器继续监督启动器,不需另开一个脱离监督的进程。
|
|
59
62
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
```
|
|
63
|
+
只有新实例通过带 MCP token 的健康检查才返回成功,并给出新实例标识及地址。端口变化或自动分配端口时,以返回地址为准;Web 不通过链接传递管理员令牌,须重新连接。
|
|
64
|
+
|
|
65
|
+
重启会关闭 ACP 会话,不保证等待人工回复的运行能无损继续。结果不明的执行保持阻断,不自动重试、重放或改派执行端。请求超时不代表重启没发生,先用 `agent-task status` 核对;不要盲目重复操作。
|
|
64
66
|
|
|
65
|
-
|
|
67
|
+
启动失败不会回滚配置或重放工作。查看当前 `dataDir/service.log`;确认没有运行实例持有数据后,修正私有文件,再显式 `agent-task restart`。启动器也已退出时,用原启动方式重新启动。后台管理器的“进程活动”不等于 HTTP 服务可用,以认证健康检查为准。
|
|
66
68
|
|
|
67
|
-
|
|
69
|
+
源码开发者执行 `npm ci --ignore-scripts && npm run build` 后,可用 `npm run setup -- ...` 和 `npm start -- --home ...`;npm 安装用户不需要构建。内部 `dist/main.js` 不是独立的重启入口。
|
|
68
70
|
|
|
69
71
|
## 后台运行
|
|
70
72
|
|
|
71
|
-
|
|
73
|
+
先停止前台实例,再注册并启动用户服务:
|
|
72
74
|
|
|
73
75
|
```bash
|
|
74
76
|
agent-task service install
|
|
75
77
|
agent-task status
|
|
78
|
+
agent-task restart
|
|
76
79
|
```
|
|
77
80
|
|
|
78
|
-
|
|
81
|
+
重复安装不会覆盖配置或批准执行端。同名但内容不同的服务文件会被拒绝覆盖。每个 OS 用户只管理一个同名服务。
|
|
79
82
|
|
|
80
83
|
Linux 需要可连接的 systemd 用户管理器:
|
|
81
84
|
|
|
82
85
|
```bash
|
|
83
86
|
systemctl --user status agent-task.service
|
|
84
87
|
systemctl --user stop agent-task.service
|
|
85
|
-
systemctl --user restart agent-task.service
|
|
86
88
|
journalctl --user -u agent-task.service
|
|
87
89
|
```
|
|
88
90
|
|
|
89
|
-
SSH/容器会话若没有用户 bus
|
|
91
|
+
SSH/容器会话若没有用户 bus,先配置正常用户会话,不以 root 运行。注销后存续需要管理员按策略配置 lingering,安装器不会启用。手动部署模板见 [deploy/agent-task.service.example](../deploy/agent-task.service.example)。
|
|
90
92
|
|
|
91
93
|
macOS 需要已登录的图形用户会话:
|
|
92
94
|
|
|
93
95
|
```bash
|
|
94
96
|
launchctl print gui/$(id -u)/com.agent-task.service
|
|
97
|
+
agent-task restart
|
|
95
98
|
agent-task service remove
|
|
96
|
-
agent-task service install
|
|
97
99
|
```
|
|
98
100
|
|
|
99
|
-
|
|
101
|
+
LaunchAgent 不是系统 LaunchDaemon,不保证用户注销后运行,也不等同于 Linux cgroup。两平台都不保证休眠或关机期间准点执行。服务文件日志由启动器写入当前数据目录;systemd journal 是操作系统另行管理的日志。
|
|
100
102
|
|
|
101
|
-
##
|
|
103
|
+
## 更新与卸载
|
|
102
104
|
|
|
103
|
-
重复 setup 保留配置和授权。中断安装只能显式 `agent-task setup --resume
|
|
105
|
+
重复 setup 保留配置和授权。中断安装只能显式 `agent-task setup --resume`;所有权或清单不明时停止核查,不删除数据修复。
|
|
104
106
|
|
|
105
|
-
|
|
107
|
+
调研阶段不保证旧数据兼容,也不提供普通启动迁移。更新前停服并备份安装目录和当前数据目录。更换 Node、npm prefix 或安装目录前先移除服务注册;不要在运行时删除应用包。
|
|
106
108
|
|
|
107
109
|
```bash
|
|
108
|
-
agent-task service remove
|
|
109
|
-
agent-task uninstall
|
|
110
|
+
agent-task service remove # 仅停止并移除服务注册
|
|
111
|
+
agent-task uninstall # 再移除本安装拥有的 ACP 组件
|
|
110
112
|
npm uninstall -g @realqiyan/agent-task # 最后由 npm 移除应用包
|
|
111
113
|
```
|
|
112
114
|
|
|
113
|
-
|
|
115
|
+
卸载可重复运行,保留配置、凭据、日志、任务历史、工作空间及未入清单文件,不操作全局执行端和原生认证。卸载不是凭据撤销或数据销毁,停服也不能回滚外部操作。实现见 [uninstall.mjs](../scripts/uninstall.mjs)。
|
package/docs/design-proposal.md
CHANGED
|
@@ -8,9 +8,9 @@
|
|
|
8
8
|
- 任务不依赖发起客户端持续在线,保留执行、交付、失败和缺少用户事实的记录。
|
|
9
9
|
- 协调员推进既定 DAG、解释中间结果、处理澄清并组织交付;领域分析和工具操作交给执行端。
|
|
10
10
|
- 缺少用户事实时可持久等待,回复后续接原运行,不丢失已接受工作。
|
|
11
|
-
- 提供本机 Web
|
|
11
|
+
- 提供本机 Web 控制台,管理配置、查看任务,并只控制任务的未来触发;配置保存后通过 Web 或 CLI 重启生效,支持前台及托管服务;切换数据目录不搬迁或删除旧历史。
|
|
12
12
|
|
|
13
|
-
Pi Agent、Codex、OpenCode 是 ACP 集成目标,不是嵌入本服务的领域执行器。声明支持、代码实现和真实验收必须区分。
|
|
13
|
+
Pi Agent、Codex、Claude、OpenCode 是 ACP 集成目标,不是嵌入本服务的领域执行器。声明支持、代码实现和真实验收必须区分。
|
|
14
14
|
|
|
15
15
|
## 架构选择
|
|
16
16
|
|
package/docs/release.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# npm 发布
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
npm 包为 [@realqiyan/agent-task](https://www.npmjs.com/package/@realqiyan/agent-task),发布账号 **realqiyan**,许可证 [MIT](../LICENSE)。候选版有意使用 **latest**,因此默认 `npm install -g @realqiyan/agent-task` 会安装最新候选版;这不表示已达到稳定或生产可用状态。实际完成情况见[验证说明](validation.md#npm-发布候选验证)。
|
|
4
4
|
|
|
5
5
|
版本、引擎、公开文件和 registry 设置以 [package.json](../package.json) 为准。[打包门禁](../scripts/pack-release.mjs)检查实际包清单;消费者安装不运行构建脚本。不要发布工作目录、私有环境、SQLite、运行日志、模型会话或测试证据。
|
|
6
6
|
|
|
@@ -12,7 +12,7 @@ npm run check
|
|
|
12
12
|
npm test
|
|
13
13
|
git diff --check
|
|
14
14
|
npm run pack:release
|
|
15
|
-
npm run test:install -- ./realqiyan-agent-task-0.0.1-rc.
|
|
15
|
+
npm run test:install -- ./realqiyan-agent-task-0.0.1-rc.2.tgz --service
|
|
16
16
|
```
|
|
17
17
|
|
|
18
18
|
安装验证在独立临时目录执行真正的全局 npm 安装,禁用 lifecycle scripts,不使用项目源码或编译器;再配置、前台启动、调用 Web 配置接口、重启、运行用户服务、进行认证健康检查并卸载。验证脚本不调用模型或启动 ACP 工作。仅 `--service` 会触及真实用户服务管理器,遇到已有同名服务会拒绝,不覆盖其他安装。
|
|
@@ -23,7 +23,7 @@ npm run test:install -- ./realqiyan-agent-task-0.0.1-rc.1.tgz --service
|
|
|
23
23
|
|
|
24
24
|
## GitHub Actions 门禁与受控发布
|
|
25
25
|
|
|
26
|
-
- [Validate](../.github/workflows/ci.yml):PR/master
|
|
26
|
+
- [Validate](../.github/workflows/ci.yml):PR/master 或手动触发时执行 TypeScript 检查、基础测试及构建;检查包清单。Linux 和 macOS 安装任务**不 checkout 项目源码**,只接收 tarball 和验收脚本,要求真实服务管理器及认证检查通过。
|
|
27
27
|
- [Publish npm](../.github/workflows/publish.yml):仅手动触发,限制 master 和账号 realqiyan,输入须匹配已验收包版本。再次执行全部门禁,再发布同一 tarball,不重建、不执行包脚本。发布后核查 latest 并执行默认全局安装验证。
|
|
28
28
|
- 普通 CI 没有 npm 凭据。发布作业单独获得凭据及 provenance 所需权限。并发发布被串行化。
|
|
29
29
|
|
|
@@ -31,21 +31,21 @@ npm run test:install -- ./realqiyan-agent-task-0.0.1-rc.1.tgz --service
|
|
|
31
31
|
1. 配置必要审核人及仅允许 master 部署。若要禁止发起人自审,需要另一个审核人。
|
|
32
32
|
2. 在该 Environment 中添加 **NPM_TOKEN**,使用 realqiyan 的最小发布权限、短有效期 token,允许直接发布(publish and stage),并满足 npm 的 2FA/自动发布策略。不要放入仓库文件或普通 CI 环境。
|
|
33
33
|
3. 核查分支保护及工作流修改权限。工作流中的 environment 名称本身**不自动创建审核保护**。
|
|
34
|
-
4. 将代码审查后提交到 master,再在 Actions 选择 Publish npm
|
|
34
|
+
4. 将代码审查后提交到 master,再在 Actions 选择 Publish npm,输入已验收的版本(本次为 `0.0.1-rc.2`)。包尚不存在时,应确认账号允许首次创建此名称;认证/权限失败须停止,不能换账号或跳过验证。
|
|
35
35
|
|
|
36
36
|
发布命令明确传 `--tag latest --access public --provenance`。provenance 依赖 GitHub OIDC,不代替 npm 发布授权。当前流程要求 Environment token 并检查 `npm whoami` 为 realqiyan;不宣称已经配置 npm trusted publishing。
|
|
37
37
|
|
|
38
|
-
##
|
|
38
|
+
## 经授权的本机发布
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
若由维护者明确批准在本机发布,须先完成同样的候选验收;这条路径没有 GitHub provenance。通过 npm 官方交互登录,不通过聊天、命令参数或 Git 传递 token:
|
|
41
41
|
|
|
42
42
|
```bash
|
|
43
43
|
npm login --registry=https://registry.npmjs.org
|
|
44
44
|
npm whoami --registry=https://registry.npmjs.org
|
|
45
45
|
# 必须确认输出 realqiyan,再发布已检查的 tarball:
|
|
46
|
-
npm publish ./realqiyan-agent-task-0.0.1-rc.
|
|
46
|
+
npm publish ./realqiyan-agent-task-0.0.1-rc.2.tgz --ignore-scripts \
|
|
47
47
|
--access public --tag latest --registry=https://registry.npmjs.org
|
|
48
|
-
npm view @realqiyan/agent-task@0.0.1-rc.
|
|
48
|
+
npm view @realqiyan/agent-task@0.0.1-rc.2 version license --registry=https://registry.npmjs.org
|
|
49
49
|
npm view @realqiyan/agent-task dist-tags --json --registry=https://registry.npmjs.org
|
|
50
50
|
node scripts/verify-install.mjs --registry --service
|
|
51
51
|
```
|
package/docs/technical-design.md
CHANGED
|
@@ -6,17 +6,17 @@
|
|
|
6
6
|
|
|
7
7
|
| 问题 | 代码入口 | 基础验证 |
|
|
8
8
|
| --- | --- | --- |
|
|
9
|
-
| 服务如何装配、启动和关闭 | [main.ts](../src/main.ts)、[lock.ts](../src/lock.ts) | [
|
|
9
|
+
| 服务如何装配、启动和关闭 | [main.ts](../src/main.ts)、[launcher.ts](../src/launcher.ts)、[lock.ts](../src/lock.ts) | [restart.test.mjs](../tests/restart.test.mjs)、[service.test.mjs](../tests/service.test.mjs) |
|
|
10
10
|
| MCP 有哪些工具、输入是什么 | [mcp.ts](../src/mcp.ts)、[task-contract.ts](../src/task-contract.ts) | [service.test.mjs](../tests/service.test.mjs) |
|
|
11
11
|
| Task、Run、节点的数据与推进规则 | [task-model.ts](../src/task-model.ts)、[tasks.ts](../src/tasks.ts) | [tasks.test.mjs](../tests/tasks.test.mjs) |
|
|
12
12
|
| 人工等待、回复及续接 | [human-input.ts](../src/human-input.ts) | [human-input.test.mjs](../tests/human-input.test.mjs) |
|
|
13
13
|
| durable 工具与模型接入 | [coordinator.ts](../src/coordinator.ts)、[coordinator-provider.ts](../src/coordinator-provider.ts) | [coordinator-provider.test.mjs](../tests/coordinator-provider.test.mjs) |
|
|
14
14
|
| 持久数据校验和提交 | [task-storage.ts](../src/task-storage.ts)、[coordinator-storage.ts](../src/coordinator-storage.ts) | [durable.test.mjs](../tests/durable.test.mjs)、[tasks.test.mjs](../tests/tasks.test.mjs) |
|
|
15
15
|
| ACP、worker 和进程关闭 | [acp.ts](../src/acp.ts)、[node-execution.ts](../src/node-execution.ts)、[worker.ts](../src/worker.ts) | [execution.test.mjs](../tests/execution.test.mjs)、[tasks.test.mjs](../tests/tasks.test.mjs) |
|
|
16
|
-
| 审批、环境与结果文件范围 | [permissions.ts](../src/permissions.ts)、[
|
|
16
|
+
| 审批、环境与结果文件范围 | [permissions.ts](../src/permissions.ts)、[executor-runtime.ts](../src/executor-runtime.ts)、[results.ts](../src/results.ts) | [policy.test.mjs](../tests/policy.test.mjs)、[results-admin.test.mjs](../tests/results-admin.test.mjs) |
|
|
17
17
|
| 配置、管理员注册和工作空间 | [config.ts](../src/config.ts)、[model.ts](../src/model.ts)、[admin.ts](../src/admin.ts)、[workspace.ts](../src/workspace.ts) | [config-preflight.test.mjs](../tests/config-preflight.test.mjs)、[tasks.test.mjs](../tests/tasks.test.mjs) |
|
|
18
18
|
| 控制台 HTTP 边界与页面 | [web.ts](../src/web.ts)、[http-auth.ts](../src/http-auth.ts)、[页面模块](../src/web/) | [web.test.mjs](../tests/web.test.mjs) |
|
|
19
|
-
| 配置和凭据的校验、保存与下次启动 | [settings.ts](../src/settings.ts)、[settings-contract.ts](../src/settings-contract.ts)、[private-files.ts](../src/private-files.ts)、[environment.ts](../src/environment.ts) | [settings.test.mjs](../tests/settings.test.mjs)、[web.test.mjs](../tests/web.test.mjs) |
|
|
19
|
+
| 配置和凭据的校验、保存与下次启动 | [settings.ts](../src/settings.ts)、[settings-contract.ts](../src/settings-contract.ts)、[startup.ts](../src/startup.ts)、[private-files.ts](../src/private-files.ts)、[environment.ts](../src/environment.ts) | [settings.test.mjs](../tests/settings.test.mjs)、[web.test.mjs](../tests/web.test.mjs) |
|
|
20
20
|
| npm 命令、安装、启动环境与卸载 | [agent-task.mjs](../bin/agent-task.mjs)、[setup-wizard.mjs](../scripts/setup-wizard.mjs)、[setup.mjs](../scripts/setup.mjs)、[install-lib.mjs](../scripts/install-lib.mjs)、[service-runner.mjs](../scripts/service-runner.mjs)、[uninstall.mjs](../scripts/uninstall.mjs) | [install.test.mjs](../tests/install.test.mjs)、[setup.test.mjs](../tests/setup.test.mjs)、[verify-install.mjs](../scripts/verify-install.mjs) |
|
|
21
21
|
| npm 包清单与发布门禁 | [package.json](../package.json)、[pack-release.mjs](../scripts/pack-release.mjs)、[工作流](../.github/workflows/) | [发布说明](release.md) |
|
|
22
22
|
|
package/docs/validation.md
CHANGED
|
@@ -14,12 +14,18 @@ git diff --check
|
|
|
14
14
|
|
|
15
15
|
## 控制台验证
|
|
16
16
|
|
|
17
|
-
[HTTP 用例](../tests/web.test.mjs)使用真实本机服务进程验证独立管理员认证、Host/Origin
|
|
17
|
+
[HTTP 用例](../tests/web.test.mjs)使用真实本机服务进程验证独立管理员认证、Host/Origin/请求类型边界、有限任务写入、密钥不回显或进入日志,以及 Web/CLI 重启后的配置差异和目录切换不复制历史。[重启用例](../tests/restart.test.mjs)验证前台 CLI、固定端口进程更换、绑定失败不能报成功,以及持久化的不确定派发在重复重启后仍被阻断。实际任务记录来自业务服务,不使用假模型输出。
|
|
18
18
|
|
|
19
|
-
[配置用例](../tests/settings.test.mjs)
|
|
19
|
+
[配置用例](../tests/settings.test.mjs)验证私有文件边界、旧编辑拒绝、显式凭据环境、端点与密钥引用分离、服务令牌别名不能作为模型凭据、失败时保留文件,以及关闭后拒绝操作。这些用例不构成断电、磁盘满或目录同步失败窗口的完整证据。
|
|
20
20
|
|
|
21
21
|
尚未完成真实浏览器交互、CSP 执行和跨浏览器缓存/自动填充验收。需在浏览器核对:不可信任务文本不执行脚本或加载外链;离开、刷新和退出后无令牌残留;配置草稿不被轮询覆盖;密钥保存与配置保存的提示明确。HTTP 头检查不能替代这些验证,也不证明模型认证或 ACP 执行成功。
|
|
22
22
|
|
|
23
|
+
### 本次启动与注册调整
|
|
24
|
+
|
|
25
|
+
Linux x64、Node 24.15.0:35 项基础测试、TypeScript 检查及 npm 包清单门禁通过。使用本次本地 tarball 执行 `node scripts/verify-install.mjs realqiyan-agent-task-0.0.1-rc.1.tgz --service`,前台和真实 systemd 用户服务的 Web/CLI 重启、认证健康检查、后台数据目录/端口/token 变量名切换及卸载保留数据均通过。未调用模型或启动 ACP 任务,未发现验证凭据进入捕获输出。报告:`/tmp/agent-task-npm-8kUj45/report.json`。
|
|
26
|
+
|
|
27
|
+
本次尚未在 macOS 复验 launchd,历史 macOS 安装证据不代表新启动器已验收。四类执行端的固定命令映射和凭据过滤有本地契约测试;Claude ACP 新增注册目标没有真实握手、认证或任务证据。其他执行端的历史结果也不能代替新原生环境和审批策略下的真实复验。
|
|
28
|
+
|
|
23
29
|
## 真实模型验收入口
|
|
24
30
|
|
|
25
31
|
仅在获准环境显式运行,会产生模型费用;不使用个人凭据或真实交易:
|
|
@@ -67,11 +73,11 @@ Linux x64、Node 24.15.0 的本机记录:
|
|
|
67
73
|
|
|
68
74
|
证据目录:`/tmp/agent-task-deploy-verified`、`/tmp/agent-task-deploy-final`;工作空间:`/tmp/agent-task-deploy-workspace`;空认证 HOME:`/tmp/agent-task-deploy-native-home`。临时文件可能被系统清理,长期留存需管理员安全归档。
|
|
69
75
|
|
|
70
|
-
macOS
|
|
76
|
+
macOS 的 npm 安装、LaunchAgent 启停及卸载保留数据已在 GitHub macOS ARM64 runner 验证,见下文。原生认证与权限、真实任务、终端关闭/用户注销后的存续,以及执行端取消和进程清理仍未验收;CI 服务启动不能替代这些证据。
|
|
71
77
|
|
|
72
78
|
## npm 发布候选验证
|
|
73
79
|
|
|
74
|
-
2026-10-22
|
|
80
|
+
2026-10-22,`realqiyan` 已将 **[@realqiyan/agent-task@0.0.1-rc.1](https://www.npmjs.com/package/@realqiyan/agent-task/v/0.0.1-rc.1)** 发布到 npmjs。匿名 registry 查询确认版本、发布账号、MIT 许可证及 `latest=0.0.1-rc.1`。本次经授权从本机发布,没有 GitHub provenance。包内文档是打包时快照,后续证据以[仓库中的最新记录](https://github.com/realqiyan/agent-task/blob/master/docs/validation.md#npm-发布候选验证)为准。
|
|
75
81
|
|
|
76
82
|
npm 因包名保护拒绝了未加 scope 的 `agent-task`:名称被判定与 `agent-base` 过于相似。维护者已明确批准使用 `@realqiyan/agent-task`,安装命令随之改为 `npm install -g @realqiyan/agent-task`,CLI 仍为 `agent-task`。未关闭账号 2FA,也未上传本机凭据。
|
|
77
83
|
|
|
@@ -79,13 +85,15 @@ npm 因包名保护拒绝了未加 scope 的 `agent-task`:名称被判定与 `
|
|
|
79
85
|
|
|
80
86
|
- Node 22.19.0 和 24.15.0 的 TypeScript 检查、33 项基础测试及构建均通过;真实 PTY 用例验证 setup 的确认/取消、生成独立 token 和不输出凭据。
|
|
81
87
|
- [包清单门禁](../scripts/pack-release.mjs)通过:仅包含预编译 JS、Web 资源、明确列出的操作脚本、示例及公开文档;不包含源码、开发工具、凭据文件、运行数据库或本地记录。
|
|
82
|
-
- Node 22.19.0
|
|
88
|
+
- Node 22.19.0 先验收 scoped tarball,再从 npmjs 按 `npm install -g @realqiyan/agent-task` 的默认版本选择全局安装到独立临时 prefix;没有指定版本或使用本地 tarball 替代 registry 安装。禁用 lifecycle scripts,不使用项目源码或 TypeScript。前台启动、Web HTTP 配置保存/重启、真实 systemd 用户服务及带认证的健康检查均通过。
|
|
83
89
|
- 重复 setup 保留 token;重复卸载保留配置、凭据、任务库和工作空间。显式注册的执行端仍未授权,没有 ACP 工作或模型调用。捕获的命令、前台输出及用户服务日志未出现验证用凭据,测试服务已移除。
|
|
84
|
-
-
|
|
90
|
+
- tarball 预验收使用 npm 镜像依赖;发布后验收固定使用 `https://registry.npmjs.org/`,通过独立空 npm 配置文件进行匿名安装。复用 npm 下载缓存,没有复制项目 node_modules;不构成空缓存下载时延的保证。
|
|
91
|
+
|
|
92
|
+
Linux 22.19.0 的发布后 registry 安装报告:`/tmp/agent-task-npm-uM8FT5/report.json`;scoped tarball 预验收报告:`/tmp/agent-task-npm-Zgkggc/report.json`。临时目录包含验证专用随机凭据和空业务库,不上传或打包;报告仅含结果。Node 22.19.0 会输出内置 SQLite 的 experimental warning,这不是认证错误。
|
|
85
93
|
|
|
86
|
-
|
|
94
|
+
同日,[GitHub Validate 运行 39813423968](https://github.com/realqiyan/agent-task/actions/runs/39813423968)在提交 `461a1b0` 上全部通过:Node 22.19.0/24.x 检查、基础测试、构建及包清单门禁;Ubuntu x64 与 macOS ARM64 的 Node 22.19.0 安装任务均不 checkout 源码,使用 CI tarball 完成前台、Web HTTP 配置、真实 systemd/LaunchAgent、认证健康检查和卸载保留数据验收。报告确认输出无验证凭据,没有模型或 ACP 工作。macOS 的证据来自 hosted runner,不包括 Intel Mac、交互式终端/浏览器、休眠或用户注销场景。
|
|
87
95
|
|
|
88
|
-
|
|
96
|
+
[受控发布工作流](../.github/workflows/publish.yml)已推送,但发布 Environment 审核和 npm 发布 secret 尚未配置,发布作业没有实际执行;本机首发和 Validate 通过不等于 Actions 发布已经验收。
|
|
89
97
|
|
|
90
98
|
操作和发布条件见[部署说明](deployment.md)、[发布说明](release.md)。此前源码安装的历史部署证据不替代本次 npm 候选包验收。
|
|
91
99
|
|
|
@@ -94,7 +102,7 @@ npm 因包名保护拒绝了未加 scope 的 `agent-task`:名称被判定与 `
|
|
|
94
102
|
- 独立单节点用例在当前接口上的真实模型复验、复杂任务的语义验收、并行分支与汇合、不同执行端之间的交付。
|
|
95
103
|
- 各执行端原生权限、认证续期、沙箱、技能/MCP 发现及文件访问;其他模型和 OpenAI 兼容端点的协议差异。
|
|
96
104
|
- 真实模型崩溃窗口、重复收费和预算行为;人工等待期间保留真实 ACP 会话的续答、并发回复、长时间等待及重启。
|
|
97
|
-
- 系统级强杀、断电、业务库磁盘满、Linux 注销/重启与 cgroup 清理,以及 macOS
|
|
105
|
+
- 系统级强杀、断电、业务库磁盘满、Linux 注销/重启与 cgroup 清理,以及 macOS 真实任务的长期监督和原生权限。
|
|
98
106
|
- SDK 升级、历史数据转换,以及执行中修改计划等当前未开放能力。
|
|
99
107
|
|
|
100
108
|
实现和操作入口见[代码导航](technical-design.md)、[部署说明](deployment.md)。
|
package/docs/web-console.md
CHANGED
|
@@ -4,18 +4,11 @@
|
|
|
4
4
|
|
|
5
5
|
## 启用与连接
|
|
6
6
|
|
|
7
|
-
新安装由 `agent-task setup`([实现](../scripts/setup.mjs)
|
|
7
|
+
新安装由 `agent-task setup`([实现](../scripts/setup.mjs))在当前 `dataDir/environment.json` 中生成独立的 `AGENT_TASK_ADMIN_TOKEN`。用本地编辑器读取该字段并在连接表单输入;它不是 MCP token,不能放进 URL、命令参数、截图或日志。
|
|
8
8
|
|
|
9
|
-
已有文件不会由 setup 补写或覆盖。缺少管理员令牌时,管理接口关闭。需要启用时先停服,用本地密码管理器生成独立随机令牌,离线加入该字段;令牌约束见 [
|
|
9
|
+
已有文件不会由 setup 补写或覆盖。缺少管理员令牌时,管理接口关闭。需要启用时先停服,用本地密码管理器生成独立随机令牌,离线加入该字段;令牌约束见 [environment.ts](../src/environment.ts)。不要复用服务或模型密钥。
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
```bash
|
|
14
|
-
agent-task start --config /absolute/private/config.json \
|
|
15
|
-
--environment /absolute/private/environment.json
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
两个文件须属于当前用户、权限为 `0600`,所在目录须为规范绝对路径、权限为 `0700`,不接受符号链接。环境文件仅在进程启动时加载;显式文件模式不回退到 shell 凭据。文件格式和基础环境来源见 [environment.ts](../src/environment.ts)、[install-lib.mjs](../scripts/install-lib.mjs)。
|
|
11
|
+
前台和托管服务使用同一固定启动入口,见[部署说明](deployment.md)。配置和环境文件须属于当前用户、权限为 `0600`,所在目录须为规范绝对路径、权限为 `0700`,不接受符号链接。环境文件仅在运行进程启动时加载。文件格式见 [environment.ts](../src/environment.ts)。
|
|
19
12
|
|
|
20
13
|
令牌仅保留在页面内存中;退出、离开或刷新后重新连接。不设置 Cookie,不写浏览器持久存储。配置和密钥文件仍是本地明文,须按凭据保护;此方案不隔离同一 OS 用户的其他进程或恶意浏览器扩展。
|
|
21
14
|
|
|
@@ -23,19 +16,20 @@ agent-task start --config /absolute/private/config.json \
|
|
|
23
16
|
|
|
24
17
|
- 任务列表、固定 DAG、运行历史、节点进度、已接受交付、错误和问题只读。触发计划结束不等于运行成功,原始输出不等于交付。
|
|
25
18
|
- 任务写操作仅复用 `task_update`,暂停、恢复或取消未来触发。不停止当前运行,不补跑错过的触发,不回答问题或修改 DAG。
|
|
26
|
-
-
|
|
19
|
+
- 数据目录、监听端口、MCP token 环境变量名、执行端注册和协调模型可编辑,下次启动生效。修改 token 变量名保留令牌值,不在页面回显。
|
|
20
|
+
- 执行端只选择 pi、codex、claude 或 opencode,保留 ID、默认工作空间和无人值守审核,并可填写备注。启动方式由系统决定;原生认证和权限继续用官方工具管理。没有命令、参数、环境变量清单、结果目录或 ACP 工具白名单配置。
|
|
27
21
|
|
|
28
|
-
页面与接口入口见 [web.ts](../src/web.ts)、[页面模块](../src/web/),任务契约见 [task-contract.ts](../src/task-contract.ts)
|
|
22
|
+
页面与接口入口见 [web.ts](../src/web.ts)、[页面模块](../src/web/),任务契约见 [task-contract.ts](../src/task-contract.ts)。提供受管理员认证保护的重启入口;没有通用 MCP 代理、文件浏览器或下载接口。固定结果目录不授予整个 dataDir 的读取能力。
|
|
29
23
|
|
|
30
24
|
## 保存与重启
|
|
31
25
|
|
|
32
26
|
1. 密钥只显示是否设置,原值不回显。不提交即保留;替换和删除必须明确操作。先保存新密钥,再保存引用它的配置。新凭据命名和受保护变量见 [settings.ts](../src/settings.ts)。
|
|
33
|
-
2. 配置先校验并预览影响,再确认保存。校验不调用模型或启动 ACP
|
|
27
|
+
2. 配置先校验并预览影响,再确认保存。校验不调用模型或启动 ACP,不能证明认证、原生权限或实际执行成功。修改执行端类型、ID 或工作空间后,页面要求重新明确批准无人值守执行;仅修改备注不改变授权。
|
|
34
28
|
3. 配置与密钥分别原子替换,不是跨文件事务。更换模型端点须使用新的凭据引用,避免中途重启将新密钥交给旧端点。旧凭据不自动清理。
|
|
35
|
-
4.
|
|
36
|
-
5.
|
|
29
|
+
4. 保存不热更新。检查活动运行及待回答运行后,点击“重启并检查可用性”或执行 `agent-task restart`;两者均支持前台和 systemd/launchd 托管实例。目录切换、原生环境来源及故障处理见[部署说明](deployment.md)。
|
|
30
|
+
5. 只有新进程通过认证健康检查才显示成功。页面清除令牌并给出新地址,重新连接后核对运行配置。请求未确认时先用 CLI status 核查,不删除历史、自动回滚或重放执行来修复。
|
|
37
31
|
|
|
38
|
-
待回答运行也可能持有 ACP
|
|
32
|
+
待回答运行也可能持有 ACP 会话,重启不保证无损续接。执行端类型、工作空间或批准状态与任务快照不一致时,旧任务的派发会被拒绝;页面显示受影响任务,但不改写快照、DAG 或执行端指派。需要新任务时由 MCP 调用方处理。
|
|
39
33
|
|
|
40
34
|
多个页面的旧编辑会被拒绝,不做自动合并。离线编辑配置须先停服。校验与保存职责见 [settings.ts](../src/settings.ts)、[settings-contract.ts](../src/settings-contract.ts)、[private-files.ts](../src/private-files.ts)。
|
|
41
35
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@realqiyan/agent-task",
|
|
3
|
-
"version": "0.0.1-rc.
|
|
3
|
+
"version": "0.0.1-rc.2",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"author": "realqiyan",
|
|
6
6
|
"repository": {
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"test": "npm run build && node --test --test-concurrency=1 tests/*.test.mjs",
|
|
47
47
|
"test:e2e": "npm run build && node --test --test-concurrency=1 tests/e2e/deepseek-acp.test.mjs",
|
|
48
48
|
"test:e2e:human": "npm run build && node scripts/run-pi-human-e2e.mjs",
|
|
49
|
-
"start": "node
|
|
49
|
+
"start": "node bin/agent-task.mjs start",
|
|
50
50
|
"setup": "node bin/agent-task.mjs setup",
|
|
51
51
|
"prepack": "npm run build",
|
|
52
52
|
"pack:release": "node scripts/pack-release.mjs",
|
package/scripts/install-lib.mjs
CHANGED
|
@@ -5,6 +5,7 @@ import { dirname, join, resolve, isAbsolute, basename } from "node:path";
|
|
|
5
5
|
import { homedir } from "node:os";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
7
|
import { setTimeout as delay } from "node:timers/promises";
|
|
8
|
+
import { controlService } from "../dist/launcher.js";
|
|
8
9
|
|
|
9
10
|
export const packageRoot = realpathSync(fileURLToPath(new URL("..", import.meta.url)));
|
|
10
11
|
|
|
@@ -13,6 +14,7 @@ export const unit = "agent-task.service";
|
|
|
13
14
|
export const packages = {
|
|
14
15
|
pi: ["@earendil-works/pi-coding-agent@1.3.0", "pi-acp@0.0.34"],
|
|
15
16
|
codex: ["@agentclientprotocol/codex-acp@2.2.2"],
|
|
17
|
+
claude: ["@zed-industries/claude-agent-acp@0.23.1"],
|
|
16
18
|
opencode: ["opencode-ai@1.19.3"],
|
|
17
19
|
};
|
|
18
20
|
export function argumentsFor(argv) {
|
|
@@ -85,13 +87,8 @@ export function saveManifest(root, manifest) {
|
|
|
85
87
|
}
|
|
86
88
|
}
|
|
87
89
|
export function runtimeBaseEnv(manifest) {
|
|
88
|
-
//
|
|
89
|
-
return { HOME: manifest.home, PATH: [join(manifest.root, "components/node_modules/.bin"), dirname(manifest.node), "/opt/homebrew/bin", "/usr/local/bin", "/usr/bin", "/bin"].join(":"), LANG: "en_US.UTF-8" };
|
|
90
|
-
}
|
|
91
|
-
export function runtimeEnv(manifest) {
|
|
92
|
-
const values = json(join(manifest.root, "environment.json"));
|
|
93
|
-
if (!values || typeof values !== "object" || Array.isArray(values) || Object.entries(values).some(([key, value]) => !/^[A-Za-z_][A-Za-z0-9_]*$/.test(key) || ["__proto__", "constructor", "prototype"].includes(key) || typeof value !== "string" || value.includes("\0"))) throw new Error("Invalid environment.json");
|
|
94
|
-
return { ...runtimeBaseEnv(manifest), ...values };
|
|
90
|
+
// Capture the OS user environment; the runtime isolates private service values before worker creation.
|
|
91
|
+
return { ...process.env, HOME: manifest.home, PATH: [join(manifest.root, "components/node_modules/.bin"), dirname(manifest.node), process.env.PATH ?? "", "/opt/homebrew/bin", "/usr/local/bin", "/usr/bin", "/bin"].join(":"), LANG: process.env.LANG ?? "en_US.UTF-8" };
|
|
95
92
|
}
|
|
96
93
|
const xml = value => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
97
94
|
const systemdQuote = value => '"' + value.replaceAll("\\", "\\\\").replaceAll('"', '\\"').replaceAll("%", "%%").replaceAll("$", "$$") + '"';
|
|
@@ -99,33 +96,24 @@ export function serviceDefinition(manifest) {
|
|
|
99
96
|
const runner = join(manifest.app, "scripts/service-runner.mjs");
|
|
100
97
|
if (manifest.platform === "linux") return {
|
|
101
98
|
path: join(manifest.home, ".config/systemd/user", unit),
|
|
102
|
-
text: `[Unit]\nDescription=Agent Task (managed by setup)\nAfter=network.target\n\n[Service]\nType=simple\nExecStart=${[manifest.node, runner, manifest.root].map(systemdQuote).join(" ")}\nRestart=on-failure\nRestartSec=5\nTimeoutStopSec=
|
|
99
|
+
text: `[Unit]\nDescription=Agent Task (managed by setup)\nAfter=network.target\n\n[Service]\nType=simple\nExecStart=${[manifest.node, runner, manifest.root].map(systemdQuote).join(" ")}\nRestart=on-failure\nRestartSec=5\nTimeoutStopSec=45\nKillMode=control-group\nUMask=0077\n\n[Install]\nWantedBy=default.target\n`,
|
|
103
100
|
};
|
|
104
101
|
return { path: join(manifest.home, "Library/LaunchAgents", label + ".plist"), text:
|
|
105
|
-
`<?xml version="1.0" encoding="UTF-8"?>\n<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n<plist version="1.0"><dict>\n<key>Label</key><string>${label}</string>\n<key>ProgramArguments</key><array>${[manifest.node, runner, manifest.root].map(value => `<string>${xml(value)}</string>`).join("")}</array>\n<key>RunAtLoad</key><true/>\n<key>KeepAlive</key><dict><key>SuccessfulExit</key><false/></dict>\n<key>ThrottleInterval</key><integer>5</integer>\n<key>ExitTimeOut</key><integer>
|
|
102
|
+
`<?xml version="1.0" encoding="UTF-8"?>\n<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n<plist version="1.0"><dict>\n<key>Label</key><string>${label}</string>\n<key>ProgramArguments</key><array>${[manifest.node, runner, manifest.root].map(value => `<string>${xml(value)}</string>`).join("")}</array>\n<key>RunAtLoad</key><true/>\n<key>KeepAlive</key><dict><key>SuccessfulExit</key><false/></dict>\n<key>ThrottleInterval</key><integer>5</integer>\n<key>ExitTimeOut</key><integer>45</integer>\n<key>AbandonProcessGroup</key><false/>\n<key>Umask</key><integer>63</integer>\n</dict></plist>\n` };
|
|
106
103
|
}
|
|
107
104
|
export function serviceEnv() {
|
|
108
105
|
const keys = ["HOME", "PATH", "LANG", "LC_ALL", "DBUS_SESSION_BUS_ADDRESS"];
|
|
109
106
|
return { ...Object.fromEntries(keys.filter(key => process.env[key] !== undefined).map(key => [key, process.env[key]])),
|
|
110
107
|
XDG_RUNTIME_DIR: process.env.XDG_RUNTIME_DIR ?? `/run/user/${process.getuid()}` };
|
|
111
108
|
}
|
|
112
|
-
export async function checkHealth(
|
|
113
|
-
const token = env[config.tokenEnv ?? "AGENT_TASK_TOKEN"];
|
|
114
|
-
if (!token || token.length < 32) throw new Error("A valid service token is required for the health check");
|
|
109
|
+
export async function checkHealth(home, waitMs = 5000) {
|
|
115
110
|
const deadline = Date.now() + waitMs;
|
|
116
111
|
do {
|
|
117
|
-
try {
|
|
118
|
-
|
|
119
|
-
method: "POST", headers: { Authorization: "Bearer " + token, "Content-Type": "application/json", Accept: "application/json, text/event-stream" },
|
|
120
|
-
body: JSON.stringify({ jsonrpc: "2.0", id: 1, method: "tools/call", params: { name: "service_status", arguments: {} } }),
|
|
121
|
-
signal: AbortSignal.timeout(1000),
|
|
122
|
-
});
|
|
123
|
-
const body = await response.json();
|
|
124
|
-
if (response.ok && body.result?.structuredContent?.ok && body.result.structuredContent.data?.running) return;
|
|
125
|
-
} catch { /* Startup can take several seconds; never log the authenticated request. */ }
|
|
112
|
+
try { return await controlService(home, "status"); }
|
|
113
|
+
catch { /* The launcher may still be starting; never log credentials. */ }
|
|
126
114
|
await delay(200);
|
|
127
115
|
} while (Date.now() < deadline);
|
|
128
|
-
throw new Error("Authenticated MCP health check failed; inspect
|
|
116
|
+
throw new Error("Authenticated MCP health check failed; inspect private logs and startup files");
|
|
129
117
|
}
|
|
130
118
|
export function verifyServiceFile(manifest) {
|
|
131
119
|
const definition = serviceDefinition(manifest);
|
|
@@ -150,7 +138,7 @@ export function activate(manifest) {
|
|
|
150
138
|
run("launchctl", ["print", domain + "/" + label]);
|
|
151
139
|
}
|
|
152
140
|
}
|
|
153
|
-
export function deactivate(manifest) {
|
|
141
|
+
export async function deactivate(manifest) {
|
|
154
142
|
const definition = verifyServiceFile(manifest);
|
|
155
143
|
if (!existsSync(definition.path)) {
|
|
156
144
|
if (manifest.serviceInstalled) throw new Error("Managed service file is missing; verify it is unloaded before cleanup");
|
|
@@ -165,7 +153,12 @@ export function deactivate(manifest) {
|
|
|
165
153
|
const target = `gui/${process.getuid()}/${label}`;
|
|
166
154
|
const active = spawnSync("launchctl", ["print", target], { stdio: "ignore" });
|
|
167
155
|
if (active.status === 0) run("launchctl", ["bootout", target]);
|
|
168
|
-
|
|
156
|
+
// bootout can return while launchd still reports the job during graceful shutdown.
|
|
157
|
+
const deadline = Date.now() + 50_000;
|
|
158
|
+
while (spawnSync("launchctl", ["print", target], { stdio: "ignore" }).status === 0) {
|
|
159
|
+
if (Date.now() >= deadline) throw new Error("LaunchAgent is still loaded");
|
|
160
|
+
await delay(100);
|
|
161
|
+
}
|
|
169
162
|
}
|
|
170
163
|
unlinkSync(definition.path);
|
|
171
164
|
if (manifest.platform === "linux") run("systemctl", ["--user", "daemon-reload"], { env: serviceEnv() });
|
package/scripts/probe-agent.mjs
CHANGED
|
@@ -2,7 +2,10 @@
|
|
|
2
2
|
import { spawn } from "node:child_process";
|
|
3
3
|
import { Readable, Writable } from "node:stream";
|
|
4
4
|
import * as acp from "@agentclientprotocol/sdk";
|
|
5
|
-
import { loadConfig
|
|
5
|
+
import { loadConfig } from "../dist/config.js";
|
|
6
|
+
import { executorCommand, executorEnvironment } from "../dist/executor-runtime.js";
|
|
7
|
+
import { readEnvironment } from "../dist/environment.js";
|
|
8
|
+
import { join } from "node:path";
|
|
6
9
|
import { terminateGroup } from "../dist/acp.js";
|
|
7
10
|
import { version } from "../dist/version.js";
|
|
8
11
|
const [file, id] = process.argv.slice(2);
|
|
@@ -10,8 +13,10 @@ if (!file || !id) { console.error("Usage: node scripts/probe-agent.mjs <config.j
|
|
|
10
13
|
const config = loadConfig(file);
|
|
11
14
|
const executor = config.executors.find(value => value.id === id);
|
|
12
15
|
if (!executor) throw new Error("Executor not found");
|
|
13
|
-
const
|
|
14
|
-
|
|
16
|
+
const env = executorEnvironment(process.env, readEnvironment(join(config.dataDir, "environment.json")), config);
|
|
17
|
+
const launch = executorCommand(executor.type, env);
|
|
18
|
+
const child = spawn(launch.command, launch.args, {
|
|
19
|
+
cwd: executor.cwd, env, detached: true, stdio: ["pipe", "pipe", "pipe"],
|
|
15
20
|
});
|
|
16
21
|
child.stderr.resume();
|
|
17
22
|
let connection;
|