@realqiyan/agent-task 0.0.1-rc.2 → 0.1.0-rc.1
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/CONTEXT.md +17 -0
- package/README.md +30 -7
- package/bin/agent-task.mjs +35 -36
- package/config.example.json +1 -1
- package/dist/{launcher.js → application/launcher.js} +49 -58
- package/dist/application/main.js +125 -0
- package/dist/application/mcp-entry.js +71 -0
- package/dist/{startup.js → application/startup.js} +6 -11
- package/dist/application/task-runtime.js +42 -0
- package/dist/configuration/contract.js +28 -0
- package/dist/{environment.js → configuration/environment.js} +1 -5
- package/dist/configuration/files.js +42 -0
- package/dist/configuration/management.js +6 -0
- package/dist/configuration/model.js +2 -0
- package/dist/configuration/policy.js +24 -0
- package/dist/configuration/ports.js +2 -0
- package/dist/{settings.js → configuration/settings.js} +54 -39
- package/dist/executors/environment.js +10 -0
- package/dist/executors/model.js +2 -0
- package/dist/executors/ports.js +2 -0
- package/dist/executors/registry.js +131 -0
- package/dist/executors/validation.js +9 -0
- package/dist/infrastructure/lock.js +55 -0
- package/dist/{private-files.js → infrastructure/private-files.js} +1 -7
- package/dist/infrastructure/sqlite.js +19 -0
- package/dist/infrastructure/version.js +3 -0
- package/dist/{acp.js → integrations/acp/connection.js} +13 -11
- package/dist/integrations/acp/discovery.js +271 -0
- package/dist/{node-execution.js → integrations/acp/execution.js} +1 -1
- package/dist/integrations/acp/runtime.js +43 -0
- package/dist/{worker.js → integrations/acp/worker.js} +7 -4
- package/dist/{coordinator.js → integrations/coordinator/coordinator.js} +16 -7
- package/dist/{coordinator-provider.js → integrations/coordinator/provider.js} +1 -1
- package/dist/{coordinator-storage.js → integrations/coordinator/storage.js} +2 -2
- package/dist/integrations/local-service/control.js +58 -0
- package/dist/protocol/cli/admin-client.js +45 -0
- package/dist/protocol/cli/commands.js +389 -0
- package/dist/{http-auth.js → protocol/http/auth.js} +1 -1
- package/dist/protocol/http/server.js +119 -0
- package/dist/{web.js → protocol/http/web.js} +24 -17
- package/dist/{mcp.js → protocol/mcp.js} +13 -13
- package/dist/protocol/services.js +2 -0
- package/dist/protocol/settings-contract.js +37 -0
- package/dist/protocol/stdio.js +71 -0
- package/dist/protocol/task-contract.js +28 -0
- package/dist/results/files.js +70 -0
- package/dist/results/ports.js +2 -0
- package/dist/results/service.js +42 -0
- package/dist/shared/errors.js +13 -0
- package/dist/shared/limits.js +3 -0
- package/dist/shared/time.js +2 -0
- package/dist/shared/validation.js +8 -0
- package/dist/tasks/definitions.js +86 -0
- package/dist/tasks/graph.js +23 -0
- package/dist/{human-input.js → tasks/human-input.js} +8 -7
- package/dist/tasks/model.js +2 -0
- package/dist/tasks/ports.js +2 -0
- package/dist/tasks/runs.js +263 -0
- package/dist/tasks/schedule.js +36 -0
- package/dist/tasks/sessions.js +62 -0
- package/dist/tasks/storage/records.js +45 -0
- package/dist/{task-storage.js → tasks/storage/store.js} +28 -40
- package/dist/tasks/supervisor.js +227 -0
- package/dist/tasks/validation.js +24 -0
- package/dist/web/settings-view.js +46 -33
- package/docs/adr/0001-task-request-scope.md +3 -0
- package/docs/adr/0002-mcp-workspace-source.md +3 -0
- package/docs/adr/0003-adapter-installation.md +3 -0
- package/docs/architecture-guidelines.md +8 -6
- package/docs/cli.md +57 -0
- package/docs/deployment.md +46 -70
- package/docs/design-proposal.md +5 -2
- package/docs/release.md +14 -15
- package/docs/technical-design.md +49 -26
- package/docs/validation.md +92 -18
- package/docs/web-console.md +16 -11
- package/package.json +9 -7
- package/scripts/install-lib.mjs +7 -116
- package/scripts/mcp-call.mjs +1 -1
- package/scripts/probe-agent.mjs +14 -12
- package/scripts/service-runner.mjs +9 -4
- package/scripts/setup-wizard.mjs +38 -16
- package/scripts/setup.mjs +69 -108
- package/deploy/agent-task.service.example +0 -17
- package/dist/admin.js +0 -31
- package/dist/config.js +0 -72
- package/dist/executor-runtime.js +0 -37
- package/dist/lock.js +0 -43
- package/dist/main.js +0 -175
- package/dist/model.js +0 -25
- package/dist/results.js +0 -79
- package/dist/settings-contract.js +0 -16
- package/dist/task-contract.js +0 -37
- package/dist/task-model.js +0 -2
- package/dist/tasks.js +0 -508
- package/dist/version.js +0 -3
- package/scripts/uninstall.mjs +0 -57
- /package/dist/{permissions.js → executors/permissions.js} +0 -0
- /package/dist/{workspace.js → infrastructure/workspace.js} +0 -0
package/CONTEXT.md
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Agent Task
|
|
2
|
+
|
|
3
|
+
本词汇表定义任务协调与执行中的项目术语。
|
|
4
|
+
|
|
5
|
+
## Language
|
|
6
|
+
|
|
7
|
+
**协调模型服务(Coordinator Provider)**:
|
|
8
|
+
为内置协调员提供模型推理的服务。它的模型选择和凭据独立于各执行端的原生模型配置。
|
|
9
|
+
_Avoid_: 全局模型服务、执行端模型服务
|
|
10
|
+
|
|
11
|
+
**任务工作目录(Task Workspace)**:
|
|
12
|
+
任务各节点开展工作的项目目录。它在提交任务时确定,后续各次运行使用同一个目录。
|
|
13
|
+
_Avoid_: 执行端默认目录、服务工作目录
|
|
14
|
+
|
|
15
|
+
**任务请求键(Task Request Key)**:
|
|
16
|
+
由调用方提供、用于识别同一任务工作目录中一次任务创建请求的标识。不同工作目录中的相同请求键表示不同请求。
|
|
17
|
+
_Avoid_: 全局任务 ID
|
package/README.md
CHANGED
|
@@ -8,9 +8,11 @@
|
|
|
8
8
|
|
|
9
9
|
| 目的 | 入口 |
|
|
10
10
|
| --- | --- |
|
|
11
|
-
|
|
|
11
|
+
| 安装、配置、运行 | [部署说明](docs/deployment.md) |
|
|
12
12
|
| 本机控制台、配置保存与 Web/CLI 重启 | [控制台说明](docs/web-console.md) |
|
|
13
|
+
| 命令行配置、凭据和任务管理 | [CLI 说明](docs/cli.md) |
|
|
13
14
|
| 理解产品目标和架构选择 | [总体方案](docs/design-proposal.md) |
|
|
15
|
+
| 查询项目术语 | [词汇表](CONTEXT.md) |
|
|
14
16
|
| 查找实现与对应测试 | [代码导航](docs/technical-design.md) |
|
|
15
17
|
| 查询测试命令、真实验收及缺口 | [验证说明](docs/validation.md) |
|
|
16
18
|
| npm 候选验收、GitHub Actions 与受控发布 | [发布说明](docs/release.md) |
|
|
@@ -20,28 +22,49 @@
|
|
|
20
22
|
|
|
21
23
|
## 使用入口
|
|
22
24
|
|
|
23
|
-
Linux/macOS,Node.js **22.19
|
|
25
|
+
Linux/macOS,Node.js **22.19+**。以下描述 `0.1.0-rc.1` 的使用入口;安装包自带运行产物。实际发布状态及平台验收范围见[发布证据](docs/validation.md#npm-发布候选验证)。
|
|
24
26
|
|
|
25
27
|
```bash
|
|
26
28
|
npm install -g @realqiyan/agent-task
|
|
27
29
|
agent-task setup
|
|
28
|
-
agent-task
|
|
29
|
-
#
|
|
30
|
+
# 在 MCP 宿主中配置 command: agent-task,args: [mcp]
|
|
31
|
+
# 连接后可查询后台状态:
|
|
30
32
|
agent-task status
|
|
31
33
|
# 应用已保存配置并确认新实例可用:
|
|
32
34
|
agent-task restart
|
|
33
35
|
```
|
|
34
36
|
|
|
35
|
-
npm 包名为 `@realqiyan/agent-task`,安装后的命令仍为 `agent-task`。setup
|
|
37
|
+
npm 包名为 `@realqiyan/agent-task`,安装后的命令仍为 `agent-task`。setup 要求确认目录、端口、令牌与协调模型。MCP 连接自动启动或复用独立后台,检测本机执行 CLI,并在缺少 ACP 适配器时安装当时最新版;已有适配器复用,原生登录仍由其官方工具管理。宿主退出后后台继续运行。接入配置与前台管理方式见[部署说明](docs/deployment.md)。
|
|
36
38
|
|
|
37
|
-
|
|
39
|
+
stdio 入口从私有文件读取内部服务 token;直接 HTTP 接入需配置独立服务 token。源码开发辅助命令(凭据须安全注入环境,不写命令行):
|
|
38
40
|
|
|
39
41
|
```bash
|
|
40
42
|
node scripts/mcp-call.mjs --list
|
|
41
43
|
node scripts/mcp-call.mjs service_status
|
|
42
44
|
```
|
|
43
45
|
|
|
44
|
-
任务由 `task_create` 提交固定 DAG
|
|
46
|
+
任务由 `task_create` 提交固定 DAG,每个节点选择已就绪执行端。显式工作目录优先,否则 stdio 入口补入自己的启动目录;直接 HTTP 调用必须提供目录。具体契约见 [task-contract.ts](src/protocol/task-contract.ts),管理工具见 [mcp.ts](src/protocol/mcp.ts)。Prompt、人工回复和工作空间选择都不授予工具权限。
|
|
47
|
+
|
|
48
|
+
## CLI 命令索引
|
|
49
|
+
|
|
50
|
+
下表列出全部命令及子命令。参数见 `agent-task <命令> --help`,操作流程见 [CLI 说明](docs/cli.md)和[部署说明](docs/deployment.md)。
|
|
51
|
+
|
|
52
|
+
| 命令 | 子命令 | 用途 |
|
|
53
|
+
| --- | --- | --- |
|
|
54
|
+
| `agent-task setup` | — | 初始化、检查或接续安装配置 |
|
|
55
|
+
| `agent-task mcp` | — | stdio 接入,自动启动或连接共用后台 |
|
|
56
|
+
| `agent-task start` | — | 前台启动服务并调度任务 |
|
|
57
|
+
| `agent-task stop` | — | 停止共用服务 |
|
|
58
|
+
| `agent-task restart` | — | 应用已保存配置并重启、检查可用性 |
|
|
59
|
+
| `agent-task status` | — | 查看服务健康、活动任务及待生效配置 |
|
|
60
|
+
| `agent-task config` | `show`、`diff`、`edit`、`export`、`validate`、`apply` | 查看、编辑、导出、校验及保存配置 |
|
|
61
|
+
| `agent-task executor` | `list`、`show`、`refresh`、`upgrade`、`enable`、`disable` | 查看与刷新执行端、升级适配器、设置启停 |
|
|
62
|
+
| `agent-task coordinator` | `show`、`set`、`clear` | 查看、设置或清除协调模型配置 |
|
|
63
|
+
| `agent-task credential` | `list`、`set`、`delete` | 查看凭据状态、设置或删除凭据 |
|
|
64
|
+
| `agent-task task` | `list`、`show`、`pause`、`resume`、`cancel` | 查看任务,控制未来触发 |
|
|
65
|
+
| `agent-task run` | `list`、`show` | 查看运行历史与交付详情 |
|
|
66
|
+
| `agent-task help` / `agent-task --help` | — | 查看总帮助 |
|
|
67
|
+
| `agent-task --version` | — | 查看版本 |
|
|
45
68
|
|
|
46
69
|
## 许可证
|
|
47
70
|
|
package/bin/agent-task.mjs
CHANGED
|
@@ -3,17 +3,20 @@ import { readFileSync } from "node:fs";
|
|
|
3
3
|
|
|
4
4
|
const help = `Usage: agent-task <command>
|
|
5
5
|
setup [options] Interactive configuration (setup --help for flags)
|
|
6
|
+
mcp [--home PATH] MCP stdio connection; start or reuse the shared background service
|
|
6
7
|
start [--home PATH] Foreground service using the private installation
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
8
|
+
stop [--home PATH] Stop the shared service
|
|
9
|
+
restart [--home PATH] Restart runtime; verify readiness
|
|
10
|
+
status [--home PATH] Health, active work and pending configuration
|
|
11
|
+
config Show, edit, validate and apply configuration
|
|
12
|
+
executor Discover executors, upgrade adapters and configure enable/disable
|
|
13
|
+
coordinator Configure the coordination model
|
|
14
|
+
credential List, replace or delete private credentials
|
|
15
|
+
task Inspect tasks and control future triggers
|
|
16
|
+
run Inspect run history and deliveries
|
|
17
|
+
<command> --help Command usage; management supports --json
|
|
15
18
|
--version | --help
|
|
16
|
-
Node.js 22.19+; Linux or macOS
|
|
19
|
+
Node.js 22.19+; Linux or macOS.`;
|
|
17
20
|
try {
|
|
18
21
|
const [major, minor] = process.versions.node.split(".").map(Number);
|
|
19
22
|
if (major < 22 || (major === 22 && minor < 19)) throw new Error("Node.js 22.19+ is required");
|
|
@@ -22,40 +25,36 @@ try {
|
|
|
22
25
|
console.log(help);
|
|
23
26
|
} else if (command === "--version") {
|
|
24
27
|
console.log(JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")).version);
|
|
25
|
-
} else if (command === "setup"
|
|
28
|
+
} else if (command === "setup") {
|
|
26
29
|
process.argv = [process.execPath, command, ...args];
|
|
27
30
|
await import(`../scripts/${command}.mjs`);
|
|
28
31
|
} else {
|
|
29
32
|
const lib = await import("../scripts/install-lib.mjs");
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
if (lib.readManifest(root).serviceInstalled) throw new Error("Remove the managed service before starting a foreground instance");
|
|
50
|
-
process.argv = [process.execPath, "service-runner", root];
|
|
51
|
-
await import("../scripts/service-runner.mjs");
|
|
33
|
+
if (["start", "mcp", "stop"].includes(command)) {
|
|
34
|
+
if (args.includes("--help")) { console.log("Usage: agent-task " + command + " [--home ABSOLUTE_DIR]" + (command === "stop" ? " [--json]" : "")); }
|
|
35
|
+
else {
|
|
36
|
+
const json = command === "stop" && args.includes("--json");
|
|
37
|
+
if (args.filter(arg => arg === "--json").length > 1) throw new Error("Repeated option: --json");
|
|
38
|
+
const options = lib.argumentsFor(json ? args.filter(arg => arg !== "--json") : args);
|
|
39
|
+
if (Object.keys(options).some(key => key !== "home")) throw new Error("Only --home is accepted here");
|
|
40
|
+
const root = lib.installRoot(options);
|
|
41
|
+
if (command === "mcp") {
|
|
42
|
+
const { runMcp } = await import("../dist/application/mcp-entry.js");
|
|
43
|
+
await runMcp(root);
|
|
44
|
+
} else if (command === "stop") {
|
|
45
|
+
const { controlService } = await import("../dist/integrations/local-service/control.js");
|
|
46
|
+
console.log(JSON.stringify({ event: "stopped", ...await controlService(root, "stop") }, null, json ? undefined : 2));
|
|
47
|
+
} else {
|
|
48
|
+
process.argv = [process.execPath, "service-runner", root];
|
|
49
|
+
await import("../scripts/service-runner.mjs");
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
52
|
} else {
|
|
53
|
-
const {
|
|
54
|
-
|
|
55
|
-
console.log(JSON.stringify({ event: command === "restart" ? "restarted" : "healthy", ...ready }));
|
|
53
|
+
const { runCli } = await import("../dist/protocol/cli/commands.js");
|
|
54
|
+
await runCli(command, args, home => lib.installRoot({ home }));
|
|
56
55
|
}
|
|
57
56
|
}
|
|
58
57
|
} catch (error) {
|
|
59
|
-
console.error(error.message);
|
|
58
|
+
console.error(process.argv.includes("--json") ? JSON.stringify({ error: error.message }) : error.message);
|
|
60
59
|
process.exitCode = 1;
|
|
61
60
|
}
|
package/config.example.json
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import { fork } from "node:child_process";
|
|
2
|
-
import { createServer
|
|
2
|
+
import { createServer } from "node:http";
|
|
3
3
|
import { appendFileSync, chmodSync, closeSync, constants, existsSync, fstatSync, lstatSync, openSync, unlinkSync } from "node:fs";
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
import { randomUUID } from "node:crypto";
|
|
7
|
-
import { acquireLauncherLock } from "
|
|
8
|
-
import { prepareStartup
|
|
9
|
-
import {
|
|
10
|
-
import { loadConfig } from "./config.js";
|
|
7
|
+
import { acquireLauncherLock } from "../infrastructure/lock.js";
|
|
8
|
+
import { prepareStartup } from "./startup.js";
|
|
9
|
+
import { startupPaths } from "../configuration/files.js";
|
|
11
10
|
function message(child, type, timeout = 30_000) {
|
|
12
11
|
return new Promise((resolve, reject) => {
|
|
13
12
|
const cleanup = () => { clearTimeout(timer); child.off("message", receive); child.off("exit", exit); child.off("error", fail); };
|
|
@@ -67,17 +66,25 @@ function logFile(path) {
|
|
|
67
66
|
export async function launch(home, nativeEnvironment) {
|
|
68
67
|
process.umask(0o077);
|
|
69
68
|
startupPaths(home); // Also checks private ownership of the locator.
|
|
70
|
-
const unlock = acquireLauncherLock(home);
|
|
69
|
+
const unlock = await acquireLauncherLock(home);
|
|
71
70
|
const socket = join(home, "control.sock"), children = new Set();
|
|
72
71
|
let current, ready, restarting = true, closing = false;
|
|
72
|
+
let shutting;
|
|
73
73
|
let release;
|
|
74
74
|
const finished = new Promise(resolve => { release = resolve; });
|
|
75
75
|
const control = createServer(async (req, res) => {
|
|
76
76
|
try {
|
|
77
|
-
if (req.method !== "POST" || !["/status", "/restart"].includes(req.url ?? "")) {
|
|
77
|
+
if (req.method !== "POST" || !["/status", "/restart", "/stop"].includes(req.url ?? "")) {
|
|
78
78
|
res.writeHead(404).end();
|
|
79
79
|
return;
|
|
80
80
|
}
|
|
81
|
+
if (req.url === "/stop") {
|
|
82
|
+
await shutdown(true);
|
|
83
|
+
res.setHeader("Content-Type", "application/json");
|
|
84
|
+
res.setHeader("Connection", "close");
|
|
85
|
+
res.end(JSON.stringify({ stopped: true }));
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
81
88
|
const result = req.url === "/restart" ? await restart() : await status();
|
|
82
89
|
res.setHeader("Content-Type", "application/json");
|
|
83
90
|
res.end(JSON.stringify(result));
|
|
@@ -86,31 +93,36 @@ export async function launch(home, nativeEnvironment) {
|
|
|
86
93
|
res.writeHead(503).end(JSON.stringify({ error: "Service unavailable; inspect private logs and startup files" }));
|
|
87
94
|
}
|
|
88
95
|
});
|
|
89
|
-
const shutdown =
|
|
90
|
-
if (
|
|
91
|
-
return;
|
|
96
|
+
const shutdown = (drain = false) => {
|
|
97
|
+
if (shutting)
|
|
98
|
+
return shutting;
|
|
92
99
|
closing = true;
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
100
|
+
return shutting = (async () => {
|
|
101
|
+
control.close();
|
|
102
|
+
if (!drain)
|
|
103
|
+
control.closeAllConnections();
|
|
104
|
+
await Promise.all([...children].map(stop));
|
|
105
|
+
try {
|
|
106
|
+
removeSocket(socket);
|
|
107
|
+
}
|
|
108
|
+
finally {
|
|
109
|
+
unlock();
|
|
110
|
+
release();
|
|
111
|
+
}
|
|
112
|
+
})();
|
|
103
113
|
};
|
|
104
114
|
const signal = () => { void shutdown(); };
|
|
115
|
+
const hangup = () => { }; // A detached launcher survives the originating terminal closing.
|
|
105
116
|
process.on("SIGINT", signal);
|
|
106
117
|
process.on("SIGTERM", signal);
|
|
118
|
+
process.on("SIGHUP", hangup);
|
|
107
119
|
const start = async () => {
|
|
108
120
|
const inputs = await prepareStartup(home);
|
|
109
121
|
if (closing)
|
|
110
122
|
throw new Error("Launcher is closing");
|
|
111
123
|
const log = logFile(join(inputs.dataDir, "service.log"));
|
|
112
124
|
const child = fork(fileURLToPath(new URL("./main.js", import.meta.url)), ["--config", inputs.configPath, "--environment", inputs.environmentPath], {
|
|
113
|
-
env: { ...nativeEnvironment, AGENT_TASK_INSTANCE_ID: randomUUID() }, execArgv: [], stdio: ["ignore", "pipe", "pipe", "ipc"],
|
|
125
|
+
env: { ...nativeEnvironment, AGENT_TASK_INSTANCE_ID: randomUUID(), AGENT_TASK_INSTALL_HOME: home }, execArgv: [], stdio: ["ignore", "pipe", "pipe", "ipc"],
|
|
114
126
|
});
|
|
115
127
|
children.add(child);
|
|
116
128
|
const output = (chunk) => { appendFileSync(log, chunk); process.stdout.write(chunk); };
|
|
@@ -144,7 +156,7 @@ export async function launch(home, nativeEnvironment) {
|
|
|
144
156
|
const value = (await message(child, "ready")).ready;
|
|
145
157
|
await health(value, inputs.environment[inputs.config.tokenEnv]);
|
|
146
158
|
current = child;
|
|
147
|
-
ready = value;
|
|
159
|
+
ready = { info: value, token: inputs.environment[inputs.config.tokenEnv] };
|
|
148
160
|
return value;
|
|
149
161
|
}
|
|
150
162
|
catch (error) {
|
|
@@ -155,28 +167,35 @@ export async function launch(home, nativeEnvironment) {
|
|
|
155
167
|
const status = async () => {
|
|
156
168
|
if (!ready || restarting || closing)
|
|
157
169
|
throw new Error("Service unavailable");
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
170
|
+
// Saved inputs may need repair. Health belongs to the running instance,
|
|
171
|
+
// including the credential with which that instance actually started.
|
|
172
|
+
const running = ready;
|
|
173
|
+
await health(running.info, running.token);
|
|
174
|
+
return running.info;
|
|
161
175
|
};
|
|
162
176
|
const restart = async (requester) => {
|
|
163
177
|
if (restarting || closing)
|
|
164
178
|
throw new Error("Restart already in progress or closed");
|
|
165
179
|
restarting = true;
|
|
166
180
|
const old = current;
|
|
167
|
-
current = undefined;
|
|
168
|
-
ready = undefined;
|
|
169
181
|
try {
|
|
170
182
|
if (old) {
|
|
171
183
|
const drained = message(old, "drained");
|
|
172
184
|
old.send({ type: "drain" });
|
|
185
|
+
let result;
|
|
173
186
|
try {
|
|
174
|
-
await drained;
|
|
187
|
+
result = await drained;
|
|
175
188
|
}
|
|
176
189
|
catch (error) {
|
|
190
|
+
current = undefined;
|
|
191
|
+
ready = undefined;
|
|
177
192
|
await stop(old);
|
|
178
193
|
throw error;
|
|
179
194
|
}
|
|
195
|
+
if (result.error)
|
|
196
|
+
throw new Error(result.error);
|
|
197
|
+
current = undefined;
|
|
198
|
+
ready = undefined;
|
|
180
199
|
// Keep only the authenticated Web response alive until the new process is verified.
|
|
181
200
|
if (old !== requester)
|
|
182
201
|
await stop(old);
|
|
@@ -186,7 +205,7 @@ export async function launch(home, nativeEnvironment) {
|
|
|
186
205
|
}
|
|
187
206
|
finally {
|
|
188
207
|
restarting = false;
|
|
189
|
-
if (requester) {
|
|
208
|
+
if (requester && requester !== current) {
|
|
190
209
|
const timer = setTimeout(() => { void stop(requester); }, 5000);
|
|
191
210
|
requester.once("close", () => clearTimeout(timer));
|
|
192
211
|
}
|
|
@@ -204,35 +223,7 @@ export async function launch(home, nativeEnvironment) {
|
|
|
204
223
|
await shutdown();
|
|
205
224
|
process.off("SIGINT", signal);
|
|
206
225
|
process.off("SIGTERM", signal);
|
|
226
|
+
process.off("SIGHUP", hangup);
|
|
207
227
|
}
|
|
208
228
|
}
|
|
209
|
-
/** Local administrator capability: the socket lives in the private launcher directory. */
|
|
210
|
-
export function controlService(home, action) {
|
|
211
|
-
startupPaths(home);
|
|
212
|
-
return new Promise((resolve, reject) => {
|
|
213
|
-
let timedOut = false;
|
|
214
|
-
const req = request({ socketPath: join(home, "control.sock"), path: "/" + action, method: "POST" }, res => {
|
|
215
|
-
let body = "";
|
|
216
|
-
res.setEncoding("utf8");
|
|
217
|
-
res.on("data", chunk => { body += chunk; });
|
|
218
|
-
res.on("end", () => {
|
|
219
|
-
if (res.statusCode !== 200) {
|
|
220
|
-
reject(new Error("Service unavailable; inspect private logs and startup files"));
|
|
221
|
-
return;
|
|
222
|
-
}
|
|
223
|
-
try {
|
|
224
|
-
resolve(JSON.parse(body));
|
|
225
|
-
}
|
|
226
|
-
catch {
|
|
227
|
-
reject(new Error("Invalid launcher response"));
|
|
228
|
-
}
|
|
229
|
-
});
|
|
230
|
-
});
|
|
231
|
-
req.setTimeout(60_000, () => { timedOut = true; req.destroy(); });
|
|
232
|
-
req.once("error", () => reject(new Error(timedOut
|
|
233
|
-
? "Service control timed out; outcome is unknown, check status before another restart"
|
|
234
|
-
: "Cannot contact launcher; start the foreground or managed service first")));
|
|
235
|
-
req.end();
|
|
236
|
-
});
|
|
237
|
-
}
|
|
238
229
|
//# sourceMappingURL=launcher.js.map
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { HttpServer } from "../protocol/http/server.js";
|
|
2
|
+
import { createTaskRuntime } from "./task-runtime.js";
|
|
3
|
+
import { LocalSettingsFiles, configurationPaths } from "../configuration/files.js";
|
|
4
|
+
import { dirname, join, resolve } from "node:path";
|
|
5
|
+
import { randomUUID } from "node:crypto";
|
|
6
|
+
import { loadConfig } from "../configuration/files.js";
|
|
7
|
+
import { baseEnvironment, validateServiceEnvironment } from "../configuration/environment.js";
|
|
8
|
+
import { readEnvironment } from "../configuration/files.js";
|
|
9
|
+
import { Settings } from "../configuration/settings.js";
|
|
10
|
+
import { acquireLock } from "../infrastructure/lock.js";
|
|
11
|
+
import { errorText } from "../shared/errors.js";
|
|
12
|
+
import { createExecution } from "../integrations/acp/execution.js";
|
|
13
|
+
import { executorEnvironment } from "../executors/environment.js";
|
|
14
|
+
import { createCoordinator } from "../integrations/coordinator/coordinator.js";
|
|
15
|
+
import { LocalExecutors } from "../integrations/acp/discovery.js";
|
|
16
|
+
process.umask(0o077);
|
|
17
|
+
const args = process.argv.slice(2);
|
|
18
|
+
if (args[0] !== "--config" || args.length !== 4 || args[2] !== "--environment") {
|
|
19
|
+
console.error("Internal runtime requires --config PATH --environment PATH; use agent-task start");
|
|
20
|
+
process.exit(2);
|
|
21
|
+
}
|
|
22
|
+
const configPath = resolve(args[1]), environmentPath = resolve(args[3]);
|
|
23
|
+
const native = { ...process.env }, base = baseEnvironment(native);
|
|
24
|
+
const environment = readEnvironment(environmentPath);
|
|
25
|
+
const instanceId = process.env.AGENT_TASK_INSTANCE_ID ?? randomUUID();
|
|
26
|
+
for (const key of Object.keys(process.env))
|
|
27
|
+
delete process.env[key];
|
|
28
|
+
Object.assign(process.env, base, environment);
|
|
29
|
+
const config = loadConfig(configPath);
|
|
30
|
+
const { token, adminToken } = validateServiceEnvironment(config, environment);
|
|
31
|
+
let http;
|
|
32
|
+
let tasks, settings;
|
|
33
|
+
let release;
|
|
34
|
+
let shutting;
|
|
35
|
+
function shutdown(code, drain = false) {
|
|
36
|
+
process.exitCode = Math.max(process.exitCode ? Number(process.exitCode) : 0, code);
|
|
37
|
+
return shutting ??= (async () => {
|
|
38
|
+
http?.stopAccepting();
|
|
39
|
+
settings?.close();
|
|
40
|
+
try {
|
|
41
|
+
await tasks?.close();
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
console.error(errorText(error));
|
|
45
|
+
process.exitCode = 1;
|
|
46
|
+
}
|
|
47
|
+
await http?.close(drain);
|
|
48
|
+
await release?.();
|
|
49
|
+
})().catch(error => { console.error(errorText(error)); process.exitCode = 1; });
|
|
50
|
+
}
|
|
51
|
+
const fatal = (error) => { console.error(errorText(error)); void shutdown(1).finally(() => { if (process.connected)
|
|
52
|
+
process.disconnect(); }); };
|
|
53
|
+
let restartPending = false;
|
|
54
|
+
const restart = async () => {
|
|
55
|
+
if (!process.connected || restartPending || shutting)
|
|
56
|
+
throw new Error("Restart unavailable");
|
|
57
|
+
restartPending = true;
|
|
58
|
+
return new Promise((resolve, reject) => {
|
|
59
|
+
process.once("message", function receive(value) {
|
|
60
|
+
if (value.type !== "restarted") {
|
|
61
|
+
process.once("message", receive);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
if (value.error && !shutting) {
|
|
65
|
+
restartPending = false;
|
|
66
|
+
reject(new Error(value.error));
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
// The HTTP adapter finishes the authenticated response before this child exits.
|
|
70
|
+
setTimeout(() => { if (process.connected)
|
|
71
|
+
process.disconnect(); http?.closeConnections(); }, 200);
|
|
72
|
+
if (value.error)
|
|
73
|
+
reject(new Error(value.error));
|
|
74
|
+
else
|
|
75
|
+
resolve(value.ready);
|
|
76
|
+
});
|
|
77
|
+
process.send({ type: "restart" });
|
|
78
|
+
});
|
|
79
|
+
};
|
|
80
|
+
process.on("message", (value) => {
|
|
81
|
+
if (value.type !== "drain")
|
|
82
|
+
return;
|
|
83
|
+
// Both Web and CLI restarts pass this boundary before changing runtime ownership.
|
|
84
|
+
try {
|
|
85
|
+
if (settings?.status().writeBlocked)
|
|
86
|
+
throw new Error("Configuration publication is uncertain");
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
process.send?.({ type: "drained", error: "Restart blocked; stop and inspect private configuration files" });
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
void shutdown(0, true).then(() => {
|
|
93
|
+
if (process.exitCode) {
|
|
94
|
+
if (process.connected)
|
|
95
|
+
process.disconnect();
|
|
96
|
+
}
|
|
97
|
+
else
|
|
98
|
+
process.send?.({ type: "drained" });
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
const stop = () => { void shutdown(0).finally(() => { http?.closeConnections(); if (process.connected)
|
|
102
|
+
process.disconnect(); }); };
|
|
103
|
+
process.once("SIGINT", stop);
|
|
104
|
+
process.once("SIGTERM", stop);
|
|
105
|
+
process.once("disconnect", stop);
|
|
106
|
+
try {
|
|
107
|
+
release = await acquireLock(config.dataDir);
|
|
108
|
+
const executorEnv = executorEnvironment(native, environment, config);
|
|
109
|
+
const discovery = new LocalExecutors(join(native.AGENT_TASK_INSTALL_HOME ?? dirname(configPath), "adapters"), executorEnv);
|
|
110
|
+
tasks = createTaskRuntime(config.dataDir, [], createExecution(executorEnv), config.coordinator ? createCoordinator(config.dataDir, config.coordinator) : undefined, fatal, discovery, config.disabledExecutors);
|
|
111
|
+
if (adminToken)
|
|
112
|
+
settings = new Settings(new LocalSettingsFiles(configPath, environmentPath), configurationPaths, config, base, process.env);
|
|
113
|
+
http = new HttpServer({ tasks, settings, token, adminToken, fatal, restart, instanceId });
|
|
114
|
+
const origin = await http.listen(config.port);
|
|
115
|
+
if (shutting)
|
|
116
|
+
throw new Error("Startup stopped");
|
|
117
|
+
tasks.supervisor.start();
|
|
118
|
+
const ready = { instanceId, pid: process.pid, url: origin + "/mcp", consoleUrl: settings ? origin + "/admin/" : null };
|
|
119
|
+
console.log(JSON.stringify({ event: "ready", ...ready }));
|
|
120
|
+
process.send?.({ type: "ready", ready });
|
|
121
|
+
}
|
|
122
|
+
catch (error) {
|
|
123
|
+
fatal(error);
|
|
124
|
+
}
|
|
125
|
+
//# sourceMappingURL=main.js.map
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { closeSync, constants, existsSync, fstatSync, openSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { setTimeout } from "node:timers/promises";
|
|
6
|
+
import { configSchema } from "../configuration/contract.js";
|
|
7
|
+
import { readEnvironment, startupPaths } from "../configuration/files.js";
|
|
8
|
+
import { readPrivateText } from "../infrastructure/private-files.js";
|
|
9
|
+
import { controlService, ControlError } from "../integrations/local-service/control.js";
|
|
10
|
+
import { serveStdio } from "../protocol/stdio.js";
|
|
11
|
+
async function startBackground(home) {
|
|
12
|
+
const fd = openSync(join(home, "launcher.log"), constants.O_WRONLY | constants.O_APPEND | constants.O_CREAT | constants.O_NOFOLLOW, 0o600);
|
|
13
|
+
try {
|
|
14
|
+
const stat = fstatSync(fd);
|
|
15
|
+
if (!stat.isFile() || stat.uid !== process.getuid() || (stat.mode & 0o077))
|
|
16
|
+
throw new Error("Expected a private launcher log");
|
|
17
|
+
const child = spawn(process.execPath, [fileURLToPath(new URL("../../scripts/service-runner.mjs", import.meta.url)), home], {
|
|
18
|
+
cwd: home, env: { ...process.env }, detached: true, stdio: ["ignore", fd, fd],
|
|
19
|
+
});
|
|
20
|
+
await new Promise((resolve, reject) => {
|
|
21
|
+
child.once("error", () => reject(new Error("Cannot start background service; inspect the private launcher log")));
|
|
22
|
+
child.once("spawn", () => { child.unref(); resolve(); });
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
finally {
|
|
26
|
+
closeSync(fd);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
async function connectBackground(home) {
|
|
30
|
+
const deadline = Date.now() + 40_000;
|
|
31
|
+
let mayStart = true;
|
|
32
|
+
while (Date.now() < deadline) {
|
|
33
|
+
try {
|
|
34
|
+
return await controlService(home, "status", 2000);
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
if (!(error instanceof ControlError) || error.kind === "invalid")
|
|
38
|
+
throw error;
|
|
39
|
+
if (error.kind === "unreachable" && mayStart) {
|
|
40
|
+
mayStart = false;
|
|
41
|
+
await startBackground(home);
|
|
42
|
+
}
|
|
43
|
+
else
|
|
44
|
+
mayStart = false;
|
|
45
|
+
}
|
|
46
|
+
// Concurrent clients may launch together. The existing launcher lock selects one owner.
|
|
47
|
+
await setTimeout(200);
|
|
48
|
+
}
|
|
49
|
+
throw new Error("Background service did not become ready; inspect the private launcher log and use agent-task status. No restart was attempted");
|
|
50
|
+
}
|
|
51
|
+
/** The stdio connection owns neither the shared launcher nor persisted work. */
|
|
52
|
+
export async function runMcp(home) {
|
|
53
|
+
const workspace = process.cwd();
|
|
54
|
+
if (!existsSync(join(home, "launch.json")))
|
|
55
|
+
throw new Error("Agent Task is not initialized; run agent-task setup first");
|
|
56
|
+
startupPaths(home);
|
|
57
|
+
const ready = await connectBackground(home);
|
|
58
|
+
const paths = startupPaths(home);
|
|
59
|
+
let config;
|
|
60
|
+
try {
|
|
61
|
+
config = configSchema.parse(JSON.parse(readPrivateText(paths.configPath)));
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
throw new Error("Cannot read service configuration; inspect the private startup files");
|
|
65
|
+
}
|
|
66
|
+
const token = readEnvironment(paths.environmentPath)[config.tokenEnv];
|
|
67
|
+
if (!token)
|
|
68
|
+
throw new Error("Service credential is unavailable; inspect the private startup files");
|
|
69
|
+
await serveStdio({ url: ready.url, token, workspace });
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=mcp-entry.js.map
|
|
@@ -1,16 +1,11 @@
|
|
|
1
|
+
import { startupPaths } from "../configuration/files.js";
|
|
1
2
|
import { existsSync, mkdirSync, writeFileSync, openSync, fsyncSync, closeSync } from "node:fs";
|
|
2
3
|
import { join } from "node:path";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { checkDataDirectory, readPrivateText, replacePrivateText } from "
|
|
7
|
-
import { acquireLock } from "
|
|
8
|
-
/** A stable launch locator is the only runtime input outside the selected dataDir. */
|
|
9
|
-
export function startupPaths(home) {
|
|
10
|
-
const { dataDir } = z.object({ dataDir: z.string() }).strict().parse(JSON.parse(readPrivateText(join(home, "launch.json"))));
|
|
11
|
-
checkDataDirectory(dataDir);
|
|
12
|
-
return { dataDir, configPath: join(dataDir, "config.json"), environmentPath: join(dataDir, "environment.json") };
|
|
13
|
-
}
|
|
4
|
+
import { loadConfig } from "../configuration/files.js";
|
|
5
|
+
import { readEnvironment } from "../configuration/files.js";
|
|
6
|
+
import { validateServiceEnvironment } from "../configuration/environment.js";
|
|
7
|
+
import { checkDataDirectory, readPrivateText, replacePrivateText } from "../infrastructure/private-files.js";
|
|
8
|
+
import { acquireLock } from "../infrastructure/lock.js";
|
|
14
9
|
function prepareFile(path, text) {
|
|
15
10
|
if (existsSync(path)) {
|
|
16
11
|
if (readPrivateText(path) !== text)
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { ExecutorRegistry } from "../executors/registry.js";
|
|
3
|
+
import { checkWorkspace, resolveWorkspace } from "../infrastructure/workspace.js";
|
|
4
|
+
import { LocalResultFiles } from "../results/files.js";
|
|
5
|
+
import { Results } from "../results/service.js";
|
|
6
|
+
import { TaskDefinitions } from "../tasks/definitions.js";
|
|
7
|
+
import { TaskRuns } from "../tasks/runs.js";
|
|
8
|
+
import { RunSupervisor } from "../tasks/supervisor.js";
|
|
9
|
+
import { TaskStore } from "../tasks/storage/store.js";
|
|
10
|
+
/** Creates and owns the business store and services; callers must stop ingress before closing. */
|
|
11
|
+
export function createTaskRuntime(directory, profiles, openExecution, coordinate, fatal, discovery, disabled = []) {
|
|
12
|
+
const store = new TaskStore(directory);
|
|
13
|
+
try {
|
|
14
|
+
const executors = new ExecutorRegistry(profiles, discovery, disabled);
|
|
15
|
+
const workspaces = { resolve: resolveWorkspace, check: checkWorkspace };
|
|
16
|
+
const results = new Results(new LocalResultFiles(join(directory, "results"), true));
|
|
17
|
+
const definitions = new TaskDefinitions(store, executors, workspaces, Boolean(coordinate));
|
|
18
|
+
const runs = new TaskRuns(store, executors, results, workspaces);
|
|
19
|
+
const supervisor = new RunSupervisor(runs, openExecution, coordinate, fatal);
|
|
20
|
+
let closing;
|
|
21
|
+
return { definitions, runs, supervisor, executors,
|
|
22
|
+
close() {
|
|
23
|
+
return closing ??= (async () => {
|
|
24
|
+
definitions.close();
|
|
25
|
+
const stopped = supervisor.close();
|
|
26
|
+
runs.close();
|
|
27
|
+
try {
|
|
28
|
+
await Promise.all([stopped, executors.close()]);
|
|
29
|
+
}
|
|
30
|
+
finally {
|
|
31
|
+
store.close();
|
|
32
|
+
}
|
|
33
|
+
})();
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
store.close();
|
|
39
|
+
throw error;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=task-runtime.js.map
|