@realqiyan/agent-task 0.0.1-rc.2 → 0.0.1-rc.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -3
- package/bin/agent-task.mjs +23 -36
- package/dist/{launcher.js → application/launcher.js} +19 -42
- package/dist/application/main.js +122 -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 +43 -0
- package/dist/configuration/management.js +6 -0
- package/dist/configuration/model.js +2 -0
- package/dist/configuration/policy.js +29 -0
- package/dist/configuration/ports.js +2 -0
- package/dist/{settings.js → configuration/settings.js} +77 -39
- package/dist/executors/environment.js +10 -0
- package/dist/executors/model.js +2 -0
- package/dist/executors/registry.js +29 -0
- package/dist/executors/validation.js +10 -0
- package/dist/{lock.js → infrastructure/lock.js} +2 -2
- 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} +9 -7
- package/dist/{node-execution.js → integrations/acp/execution.js} +1 -1
- package/dist/{executor-runtime.js → integrations/acp/runtime.js} +1 -10
- package/dist/{worker.js → integrations/acp/worker.js} +3 -2
- 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 +33 -0
- package/dist/protocol/cli/admin-client.js +45 -0
- package/dist/protocol/cli/commands.js +394 -0
- package/dist/{http-auth.js → protocol/http/auth.js} +1 -1
- package/dist/protocol/http/server.js +111 -0
- package/dist/{web.js → protocol/http/web.js} +18 -17
- package/dist/{mcp.js → protocol/mcp.js} +13 -13
- package/dist/protocol/services.js +2 -0
- package/dist/protocol/settings-contract.js +40 -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 +83 -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} +23 -36
- package/dist/tasks/supervisor.js +227 -0
- package/dist/tasks/validation.js +24 -0
- package/dist/web/settings-view.js +13 -6
- package/docs/architecture-guidelines.md +6 -4
- package/docs/cli.md +49 -0
- package/docs/deployment.md +16 -56
- package/docs/design-proposal.md +2 -2
- package/docs/release.md +14 -15
- package/docs/technical-design.md +47 -26
- package/docs/validation.md +49 -17
- package/docs/web-console.md +10 -8
- package/package.json +7 -7
- package/scripts/install-lib.mjs +4 -95
- package/scripts/mcp-call.mjs +1 -1
- package/scripts/probe-agent.mjs +6 -5
- package/scripts/service-runner.mjs +1 -1
- package/scripts/setup-wizard.mjs +1 -6
- package/scripts/setup.mjs +17 -30
- package/deploy/agent-task.service.example +0 -17
- package/dist/admin.js +0 -31
- package/dist/config.js +0 -72
- 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/README.md
CHANGED
|
@@ -8,8 +8,9 @@
|
|
|
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) |
|
|
14
15
|
| 查找实现与对应测试 | [代码导航](docs/technical-design.md) |
|
|
15
16
|
| 查询测试命令、真实验收及缺口 | [验证说明](docs/validation.md) |
|
|
@@ -32,7 +33,7 @@ agent-task status
|
|
|
32
33
|
agent-task restart
|
|
33
34
|
```
|
|
34
35
|
|
|
35
|
-
npm 包名为 `@realqiyan/agent-task`,安装后的命令仍为 `agent-task`。setup 提供交互配置;启动后可使用本机 Web
|
|
36
|
+
npm 包名为 `@realqiyan/agent-task`,安装后的命令仍为 `agent-task`。setup 提供交互配置;启动后可使用本机 Web 控制台或 [CLI](docs/cli.md),两端共用管理服务。安装器不会替用户登录或批准执行端;详细操作见[部署说明](docs/deployment.md)。
|
|
36
37
|
|
|
37
38
|
MCP 客户端使用独立服务 token。源码开发辅助命令(凭据须安全注入环境,不写命令行):
|
|
38
39
|
|
|
@@ -41,7 +42,7 @@ node scripts/mcp-call.mjs --list
|
|
|
41
42
|
node scripts/mcp-call.mjs service_status
|
|
42
43
|
```
|
|
43
44
|
|
|
44
|
-
任务由 `task_create` 提交固定 DAG,每个节点选择已注册执行端。具体契约见 [task-contract.ts](src/task-contract.ts),管理工具见 [mcp.ts](src/mcp.ts)。Prompt、人工回复和工作空间选择都不授予工具权限。
|
|
45
|
+
任务由 `task_create` 提交固定 DAG,每个节点选择已注册执行端。具体契约见 [task-contract.ts](src/protocol/task-contract.ts),管理工具见 [mcp.ts](src/protocol/mcp.ts)。Prompt、人工回复和工作空间选择都不授予工具权限。
|
|
45
46
|
|
|
46
47
|
## 许可证
|
|
47
48
|
|
package/bin/agent-task.mjs
CHANGED
|
@@ -4,16 +4,17 @@ import { readFileSync } from "node:fs";
|
|
|
4
4
|
const help = `Usage: agent-task <command>
|
|
5
5
|
setup [options] Interactive configuration (setup --help for flags)
|
|
6
6
|
start [--home PATH] Foreground service using the private installation
|
|
7
|
-
restart [--home PATH] Restart
|
|
8
|
-
status [--home PATH]
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
7
|
+
restart [--home PATH] Restart runtime; verify readiness
|
|
8
|
+
status [--home PATH] Health, active work and pending configuration
|
|
9
|
+
config Show, edit, validate and apply configuration
|
|
10
|
+
executor Manage registrations and unattended authorization
|
|
11
|
+
coordinator Configure the coordination model
|
|
12
|
+
credential List, replace or delete private credentials
|
|
13
|
+
task Inspect tasks and control future triggers
|
|
14
|
+
run Inspect run history and deliveries
|
|
15
|
+
<command> --help Command usage; management supports --json
|
|
15
16
|
--version | --help
|
|
16
|
-
Node.js 22.19+; Linux or macOS
|
|
17
|
+
Node.js 22.19+; Linux or macOS.`;
|
|
17
18
|
try {
|
|
18
19
|
const [major, minor] = process.versions.node.split(".").map(Number);
|
|
19
20
|
if (major < 22 || (major === 22 && minor < 19)) throw new Error("Node.js 22.19+ is required");
|
|
@@ -22,40 +23,26 @@ try {
|
|
|
22
23
|
console.log(help);
|
|
23
24
|
} else if (command === "--version") {
|
|
24
25
|
console.log(JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")).version);
|
|
25
|
-
} else if (command === "setup"
|
|
26
|
+
} else if (command === "setup") {
|
|
26
27
|
process.argv = [process.execPath, command, ...args];
|
|
27
28
|
await import(`../scripts/${command}.mjs`);
|
|
28
29
|
} else {
|
|
29
30
|
const lib = await import("../scripts/install-lib.mjs");
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
lib.readManifest(root);
|
|
40
|
-
const unlock = lib.operationLock(root);
|
|
41
|
-
try {
|
|
42
|
-
const manifest = lib.readManifest(root);
|
|
43
|
-
await lib.deactivate(manifest);
|
|
44
|
-
manifest.serviceInstalled = false;
|
|
45
|
-
lib.saveManifest(root, manifest);
|
|
46
|
-
console.log("Owned service removed; configuration, credentials, components and data preserved.");
|
|
47
|
-
} finally { unlock(); }
|
|
48
|
-
} else if (command === "start") {
|
|
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");
|
|
31
|
+
if (command === "start") {
|
|
32
|
+
if (args.includes("--help")) { console.log("Usage: agent-task start [--home ABSOLUTE_DIR]"); }
|
|
33
|
+
else {
|
|
34
|
+
const options = lib.argumentsFor(args);
|
|
35
|
+
if (Object.keys(options).some(key => key !== "home")) throw new Error("Only --home is accepted here");
|
|
36
|
+
const root = lib.installRoot(options);
|
|
37
|
+
process.argv = [process.execPath, "service-runner", root];
|
|
38
|
+
await import("../scripts/service-runner.mjs");
|
|
39
|
+
}
|
|
52
40
|
} else {
|
|
53
|
-
const {
|
|
54
|
-
|
|
55
|
-
console.log(JSON.stringify({ event: command === "restart" ? "restarted" : "healthy", ...ready }));
|
|
41
|
+
const { runCli } = await import("../dist/protocol/cli/commands.js");
|
|
42
|
+
await runCli(command, args, home => lib.installRoot({ home }));
|
|
56
43
|
}
|
|
57
44
|
}
|
|
58
45
|
} catch (error) {
|
|
59
|
-
console.error(error.message);
|
|
46
|
+
console.error(process.argv.includes("--json") ? JSON.stringify({ error: error.message }) : error.message);
|
|
60
47
|
process.exitCode = 1;
|
|
61
48
|
}
|
|
@@ -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); };
|
|
@@ -144,7 +143,7 @@ export async function launch(home, nativeEnvironment) {
|
|
|
144
143
|
const value = (await message(child, "ready")).ready;
|
|
145
144
|
await health(value, inputs.environment[inputs.config.tokenEnv]);
|
|
146
145
|
current = child;
|
|
147
|
-
ready = value;
|
|
146
|
+
ready = { info: value, token: inputs.environment[inputs.config.tokenEnv] };
|
|
148
147
|
return value;
|
|
149
148
|
}
|
|
150
149
|
catch (error) {
|
|
@@ -155,28 +154,35 @@ export async function launch(home, nativeEnvironment) {
|
|
|
155
154
|
const status = async () => {
|
|
156
155
|
if (!ready || restarting || closing)
|
|
157
156
|
throw new Error("Service unavailable");
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
157
|
+
// Saved inputs may need repair. Health belongs to the running instance,
|
|
158
|
+
// including the credential with which that instance actually started.
|
|
159
|
+
const running = ready;
|
|
160
|
+
await health(running.info, running.token);
|
|
161
|
+
return running.info;
|
|
161
162
|
};
|
|
162
163
|
const restart = async (requester) => {
|
|
163
164
|
if (restarting || closing)
|
|
164
165
|
throw new Error("Restart already in progress or closed");
|
|
165
166
|
restarting = true;
|
|
166
167
|
const old = current;
|
|
167
|
-
current = undefined;
|
|
168
|
-
ready = undefined;
|
|
169
168
|
try {
|
|
170
169
|
if (old) {
|
|
171
170
|
const drained = message(old, "drained");
|
|
172
171
|
old.send({ type: "drain" });
|
|
172
|
+
let result;
|
|
173
173
|
try {
|
|
174
|
-
await drained;
|
|
174
|
+
result = await drained;
|
|
175
175
|
}
|
|
176
176
|
catch (error) {
|
|
177
|
+
current = undefined;
|
|
178
|
+
ready = undefined;
|
|
177
179
|
await stop(old);
|
|
178
180
|
throw error;
|
|
179
181
|
}
|
|
182
|
+
if (result.error)
|
|
183
|
+
throw new Error(result.error);
|
|
184
|
+
current = undefined;
|
|
185
|
+
ready = undefined;
|
|
180
186
|
// Keep only the authenticated Web response alive until the new process is verified.
|
|
181
187
|
if (old !== requester)
|
|
182
188
|
await stop(old);
|
|
@@ -186,7 +192,7 @@ export async function launch(home, nativeEnvironment) {
|
|
|
186
192
|
}
|
|
187
193
|
finally {
|
|
188
194
|
restarting = false;
|
|
189
|
-
if (requester) {
|
|
195
|
+
if (requester && requester !== current) {
|
|
190
196
|
const timer = setTimeout(() => { void stop(requester); }, 5000);
|
|
191
197
|
requester.once("close", () => clearTimeout(timer));
|
|
192
198
|
}
|
|
@@ -206,33 +212,4 @@ export async function launch(home, nativeEnvironment) {
|
|
|
206
212
|
process.off("SIGTERM", signal);
|
|
207
213
|
}
|
|
208
214
|
}
|
|
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
215
|
//# sourceMappingURL=launcher.js.map
|
|
@@ -0,0 +1,122 @@
|
|
|
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 { 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
|
+
process.umask(0o077);
|
|
16
|
+
const args = process.argv.slice(2);
|
|
17
|
+
if (args[0] !== "--config" || args.length !== 4 || args[2] !== "--environment") {
|
|
18
|
+
console.error("Internal runtime requires --config PATH --environment PATH; use agent-task start");
|
|
19
|
+
process.exit(2);
|
|
20
|
+
}
|
|
21
|
+
const configPath = resolve(args[1]), environmentPath = resolve(args[3]);
|
|
22
|
+
const native = { ...process.env }, base = baseEnvironment(native);
|
|
23
|
+
const environment = readEnvironment(environmentPath);
|
|
24
|
+
const instanceId = process.env.AGENT_TASK_INSTANCE_ID ?? randomUUID();
|
|
25
|
+
for (const key of Object.keys(process.env))
|
|
26
|
+
delete process.env[key];
|
|
27
|
+
Object.assign(process.env, base, environment);
|
|
28
|
+
const config = loadConfig(configPath);
|
|
29
|
+
const { token, adminToken } = validateServiceEnvironment(config, environment);
|
|
30
|
+
let http;
|
|
31
|
+
let tasks, settings;
|
|
32
|
+
let release;
|
|
33
|
+
let shutting;
|
|
34
|
+
function shutdown(code, drain = false) {
|
|
35
|
+
process.exitCode = Math.max(process.exitCode ? Number(process.exitCode) : 0, code);
|
|
36
|
+
return shutting ??= (async () => {
|
|
37
|
+
http?.stopAccepting();
|
|
38
|
+
settings?.close();
|
|
39
|
+
try {
|
|
40
|
+
await tasks?.close();
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
console.error(errorText(error));
|
|
44
|
+
process.exitCode = 1;
|
|
45
|
+
}
|
|
46
|
+
await http?.close(drain);
|
|
47
|
+
await release?.();
|
|
48
|
+
})().catch(error => { console.error(errorText(error)); process.exitCode = 1; });
|
|
49
|
+
}
|
|
50
|
+
const fatal = (error) => { console.error(errorText(error)); void shutdown(1).finally(() => { if (process.connected)
|
|
51
|
+
process.disconnect(); }); };
|
|
52
|
+
let restartPending = false;
|
|
53
|
+
const restart = async () => {
|
|
54
|
+
if (!process.connected || restartPending || shutting)
|
|
55
|
+
throw new Error("Restart unavailable");
|
|
56
|
+
restartPending = true;
|
|
57
|
+
return new Promise((resolve, reject) => {
|
|
58
|
+
process.once("message", function receive(value) {
|
|
59
|
+
if (value.type !== "restarted") {
|
|
60
|
+
process.once("message", receive);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
if (value.error && !shutting) {
|
|
64
|
+
restartPending = false;
|
|
65
|
+
reject(new Error(value.error));
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
// The HTTP adapter finishes the authenticated response before this child exits.
|
|
69
|
+
setTimeout(() => { if (process.connected)
|
|
70
|
+
process.disconnect(); http?.closeConnections(); }, 200);
|
|
71
|
+
if (value.error)
|
|
72
|
+
reject(new Error(value.error));
|
|
73
|
+
else
|
|
74
|
+
resolve(value.ready);
|
|
75
|
+
});
|
|
76
|
+
process.send({ type: "restart" });
|
|
77
|
+
});
|
|
78
|
+
};
|
|
79
|
+
process.on("message", (value) => {
|
|
80
|
+
if (value.type !== "drain")
|
|
81
|
+
return;
|
|
82
|
+
// Both Web and CLI restarts pass this boundary before changing runtime ownership.
|
|
83
|
+
try {
|
|
84
|
+
if (settings?.status().writeBlocked)
|
|
85
|
+
throw new Error("Configuration publication is uncertain");
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
process.send?.({ type: "drained", error: "Restart blocked; stop and inspect private configuration files" });
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
void shutdown(0, true).then(() => {
|
|
92
|
+
if (process.exitCode) {
|
|
93
|
+
if (process.connected)
|
|
94
|
+
process.disconnect();
|
|
95
|
+
}
|
|
96
|
+
else
|
|
97
|
+
process.send?.({ type: "drained" });
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
const stop = () => { void shutdown(0).finally(() => { http?.closeConnections(); if (process.connected)
|
|
101
|
+
process.disconnect(); }); };
|
|
102
|
+
process.once("SIGINT", stop);
|
|
103
|
+
process.once("SIGTERM", stop);
|
|
104
|
+
process.once("disconnect", stop);
|
|
105
|
+
try {
|
|
106
|
+
release = await acquireLock(config.dataDir);
|
|
107
|
+
tasks = createTaskRuntime(config.dataDir, config.executors, createExecution(executorEnvironment(native, environment, config)), config.coordinator ? createCoordinator(config.dataDir, config.coordinator) : undefined, fatal);
|
|
108
|
+
if (adminToken)
|
|
109
|
+
settings = new Settings(new LocalSettingsFiles(configPath, environmentPath), configurationPaths, config, base, process.env);
|
|
110
|
+
http = new HttpServer({ tasks, settings, token, adminToken, fatal, restart, instanceId });
|
|
111
|
+
const origin = await http.listen(config.port);
|
|
112
|
+
if (shutting)
|
|
113
|
+
throw new Error("Startup stopped");
|
|
114
|
+
tasks.supervisor.start();
|
|
115
|
+
const ready = { instanceId, pid: process.pid, url: origin + "/mcp", consoleUrl: settings ? origin + "/admin/" : null };
|
|
116
|
+
console.log(JSON.stringify({ event: "ready", ...ready }));
|
|
117
|
+
process.send?.({ type: "ready", ready });
|
|
118
|
+
}
|
|
119
|
+
catch (error) {
|
|
120
|
+
fatal(error);
|
|
121
|
+
}
|
|
122
|
+
//# sourceMappingURL=main.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) {
|
|
12
|
+
const store = new TaskStore(directory);
|
|
13
|
+
try {
|
|
14
|
+
const executors = new ExecutorRegistry(profiles);
|
|
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 stopped;
|
|
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
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { executorSchema } from "../executors/validation.js";
|
|
3
|
+
import { baseEnvKeys } from "./policy.js";
|
|
4
|
+
const envKey = z.string().regex(/^[A-Za-z_][A-Za-z0-9_]*$/);
|
|
5
|
+
const modelId = z.string().min(1).max(200);
|
|
6
|
+
const coordinatorSchema = z.discriminatedUnion("provider", [
|
|
7
|
+
z.object({ provider: z.enum(["openai", "anthropic", "deepseek"]), model: modelId }).strict(),
|
|
8
|
+
z.object({
|
|
9
|
+
provider: z.literal("openai-compatible"), model: modelId,
|
|
10
|
+
api: z.enum(["openai-completions", "openai-responses"]).default("openai-completions"),
|
|
11
|
+
baseUrl: z.string().url().refine(value => {
|
|
12
|
+
const url = new URL(value);
|
|
13
|
+
return ["https:", "http:"].includes(url.protocol) && !url.username && !url.password && !url.search && !url.hash;
|
|
14
|
+
}, "baseUrl must be HTTP(S) without credentials, query or fragment"),
|
|
15
|
+
apiKeyEnv: envKey.refine(key => !baseEnvKeys.includes(key), "Credential must not use a base environment key"),
|
|
16
|
+
contextWindow: z.number().int().positive(),
|
|
17
|
+
maxTokens: z.number().int().positive(),
|
|
18
|
+
reasoning: z.boolean().default(false),
|
|
19
|
+
}).strict(),
|
|
20
|
+
]).refine(value => value.provider !== "openai-compatible" || value.maxTokens <= value.contextWindow, "maxTokens must not exceed contextWindow");
|
|
21
|
+
export const configSchema = z.object({
|
|
22
|
+
dataDir: z.string().min(1),
|
|
23
|
+
port: z.number().int().min(0).max(65535).default(8765),
|
|
24
|
+
tokenEnv: z.string().regex(/^[A-Za-z_][A-Za-z0-9_]*$/).default("AGENT_TASK_TOKEN"),
|
|
25
|
+
coordinator: coordinatorSchema.optional(),
|
|
26
|
+
executors: z.array(executorSchema).max(100).default([]),
|
|
27
|
+
}).strict();
|
|
28
|
+
//# sourceMappingURL=contract.js.map
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { ADMIN_TOKEN_ENV, coordinatorCredentialEnv } from "./
|
|
2
|
-
import { readPrivateText } from "./private-files.js";
|
|
1
|
+
import { ADMIN_TOKEN_ENV, coordinatorCredentialEnv } from "./policy.js";
|
|
3
2
|
export function parseEnvironment(text) {
|
|
4
3
|
let value;
|
|
5
4
|
try {
|
|
@@ -13,9 +12,6 @@ export function parseEnvironment(text) {
|
|
|
13
12
|
throw new Error("Invalid environment file");
|
|
14
13
|
return value;
|
|
15
14
|
}
|
|
16
|
-
export function readEnvironment(path) {
|
|
17
|
-
return parseEnvironment(readPrivateText(path));
|
|
18
|
-
}
|
|
19
15
|
/** Credential names and values must stay separate, including historical token aliases. */
|
|
20
16
|
export function validateServiceEnvironment(config, environment) {
|
|
21
17
|
const token = environment[config.tokenEnv], adminToken = environment[ADMIN_TOKEN_ENV];
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { checkDataDirectory, readPrivateText, replacePrivateText } from "../infrastructure/private-files.js";
|
|
4
|
+
import { resolveWorkspace } from "../infrastructure/workspace.js";
|
|
5
|
+
import { configSchema } from "./contract.js";
|
|
6
|
+
import { parseEnvironment } from "./environment.js";
|
|
7
|
+
import { validateConfig } from "./policy.js";
|
|
8
|
+
export const configurationPaths = { resolveWorkspace, checkDataDirectory };
|
|
9
|
+
export function loadConfig(path) {
|
|
10
|
+
let value;
|
|
11
|
+
try {
|
|
12
|
+
value = JSON.parse(readPrivateText(path));
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
throw new Error("Cannot read configuration JSON");
|
|
16
|
+
}
|
|
17
|
+
return validateConfig(configSchema.parse(value), resolveWorkspace);
|
|
18
|
+
}
|
|
19
|
+
export function readEnvironment(path) { return parseEnvironment(readPrivateText(path)); }
|
|
20
|
+
export class LocalSettingsFiles {
|
|
21
|
+
configPath;
|
|
22
|
+
environmentPath;
|
|
23
|
+
constructor(configPath, environmentPath) {
|
|
24
|
+
this.configPath = configPath;
|
|
25
|
+
this.environmentPath = environmentPath;
|
|
26
|
+
if (configPath === environmentPath)
|
|
27
|
+
throw new Error("Configuration and environment must be separate files");
|
|
28
|
+
}
|
|
29
|
+
read() {
|
|
30
|
+
const configText = readPrivateText(this.configPath), environmentText = readPrivateText(this.environmentPath);
|
|
31
|
+
return { configText, environmentText, config: configSchema.parse(JSON.parse(configText)), environment: parseEnvironment(environmentText) };
|
|
32
|
+
}
|
|
33
|
+
publish(target, value) {
|
|
34
|
+
replacePrivateText(target === "config" ? this.configPath : this.environmentPath, JSON.stringify(value, null, 2) + "\n");
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/** A stable launch locator is the only runtime input outside the selected dataDir. */
|
|
38
|
+
export function startupPaths(home) {
|
|
39
|
+
const { dataDir } = z.object({ dataDir: z.string() }).strict().parse(JSON.parse(readPrivateText(join(home, "launch.json"))));
|
|
40
|
+
checkDataDirectory(dataDir);
|
|
41
|
+
return { dataDir, configPath: join(dataDir, "config.json"), environmentPath: join(dataDir, "environment.json") };
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=files.js.map
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** Configuration preview and task impact are one management operation for all clients. */
|
|
2
|
+
export function previewSettings(settings, tasks, input) {
|
|
3
|
+
const preview = settings.preview(input);
|
|
4
|
+
return { ...preview, affectedTasks: tasks.executorImpact(preview.config.executors) };
|
|
5
|
+
}
|
|
6
|
+
//# sourceMappingURL=management.js.map
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export const ADMIN_TOKEN_ENV = "AGENT_TASK_ADMIN_TOKEN";
|
|
2
|
+
export const baseEnvKeys = ["HOME", "PATH", "LANG", "LC_ALL", "TZ", "TMPDIR", "NODE_OPTIONS", "NODE_PATH", "__proto__", "constructor", "prototype"];
|
|
3
|
+
export function coordinatorCredentialEnv(config) {
|
|
4
|
+
switch (config.provider) {
|
|
5
|
+
case "openai": return "OPENAI_API_KEY";
|
|
6
|
+
case "anthropic": return "ANTHROPIC_API_KEY";
|
|
7
|
+
case "deepseek": return "DEEPSEEK_API_KEY";
|
|
8
|
+
case "openai-compatible": return config.apiKeyEnv;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
export function validateConfig(value, resolveWorkspace) {
|
|
12
|
+
const config = structuredClone(value);
|
|
13
|
+
if (!config.dataDir.startsWith("/") || /[\x00-\x1f\x7f]/.test(config.dataDir))
|
|
14
|
+
throw new Error("dataDir must be absolute without control characters");
|
|
15
|
+
const coordinatorKey = config.coordinator ? coordinatorCredentialEnv(config.coordinator) : undefined;
|
|
16
|
+
if (/^(NODE_|LD_|DYLD_)/.test(config.tokenEnv) || [...baseEnvKeys, ADMIN_TOKEN_ENV, "OPENAI_API_KEY", "ANTHROPIC_API_KEY", "DEEPSEEK_API_KEY", coordinatorKey].includes(config.tokenEnv))
|
|
17
|
+
throw new Error("tokenEnv must be separate from base environment, administrator and model credentials");
|
|
18
|
+
if (coordinatorKey === ADMIN_TOKEN_ENV)
|
|
19
|
+
throw new Error("Administrator token must not be used as a model credential");
|
|
20
|
+
const ids = new Set();
|
|
21
|
+
for (const executor of config.executors) {
|
|
22
|
+
if (ids.has(executor.id))
|
|
23
|
+
throw new Error("Duplicate executor ID");
|
|
24
|
+
ids.add(executor.id);
|
|
25
|
+
executor.cwd = resolveWorkspace(executor.cwd);
|
|
26
|
+
}
|
|
27
|
+
return config;
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=policy.js.map
|