@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/docs/web-console.md
CHANGED
|
@@ -2,36 +2,41 @@
|
|
|
2
2
|
|
|
3
3
|
控制台与 MCP 共用 HTTP 服务,只监听 `127.0.0.1`。访问 `http://127.0.0.1:<port>/admin/`,不要改用 LAN 地址、反向代理或公网转发。
|
|
4
4
|
|
|
5
|
+
全部管理操作也可通过 [CLI](cli.md) 完成,两端使用同一管理 API 和底层服务。
|
|
6
|
+
|
|
5
7
|
## 启用与连接
|
|
6
8
|
|
|
7
9
|
新安装由 `agent-task setup`([实现](../scripts/setup.mjs))在当前 `dataDir/environment.json` 中生成独立的 `AGENT_TASK_ADMIN_TOKEN`。用本地编辑器读取该字段并在连接表单输入;它不是 MCP token,不能放进 URL、命令参数、截图或日志。
|
|
8
10
|
|
|
9
|
-
已有文件不会由 setup 补写或覆盖。缺少管理员令牌时,管理接口关闭。需要启用时先停服,用本地密码管理器生成独立随机令牌,离线加入该字段;令牌约束见 [environment.ts](../src/environment.ts)。不要复用服务或模型密钥。
|
|
11
|
+
已有文件不会由 setup 补写或覆盖。缺少管理员令牌时,管理接口关闭。需要启用时先停服,用本地密码管理器生成独立随机令牌,离线加入该字段;令牌约束见 [environment.ts](../src/configuration/environment.ts)。不要复用服务或模型密钥。
|
|
10
12
|
|
|
11
|
-
|
|
13
|
+
启动入口,见[部署说明](deployment.md)。配置和环境文件须属于当前用户、权限为 `0600`,所在目录须为规范绝对路径、权限为 `0700`,不接受符号链接。环境文件仅在运行进程启动时加载。文件格式见 [environment.ts](../src/configuration/environment.ts)。
|
|
12
14
|
|
|
13
15
|
令牌仅保留在页面内存中;退出、离开或刷新后重新连接。不设置 Cookie,不写浏览器持久存储。配置和密钥文件仍是本地明文,须按凭据保护;此方案不隔离同一 OS 用户的其他进程或恶意浏览器扩展。
|
|
14
16
|
|
|
15
17
|
## 操作范围
|
|
16
18
|
|
|
17
19
|
- 任务列表、固定 DAG、运行历史、节点进度、已接受交付、错误和问题只读。触发计划结束不等于运行成功,原始输出不等于交付。
|
|
18
|
-
- 任务写操作仅复用 `task_update`,暂停、恢复或取消未来触发。不停止当前运行,不补跑错过的触发,不回答问题或修改 DAG
|
|
19
|
-
-
|
|
20
|
-
-
|
|
20
|
+
- 任务写操作仅复用 `task_update`,暂停、恢复或取消未来触发。不停止当前运行,不补跑错过的触发,不回答问题或修改 DAG。已有不确定外部结果的任务拒绝恢复未来触发;当前不提供人工核对后强制恢复能力。
|
|
21
|
+
- 协调模型是常规配置入口。数据目录、监听端口、MCP token 环境变量名和执行端启停位于高级设置,下次启动生效。修改 token 变量名保留令牌值,不在页面回显。
|
|
22
|
+
- 执行端自动检测本机 pi、codex、claude 和 opencode。页面显示本机路径、准备结果和错误,提供重新检测与显式适配器升级;准备过程中自动更新状态。就绪不代表原生认证或工具权限已经验收。安装与更新边界见[部署说明](deployment.md)。
|
|
23
|
+
- 无须手工注册执行端、设置默认工作目录或另行批准。原生认证和权限继续用官方工具管理;任务工作目录通过 MCP 上下文或创建参数确定。
|
|
21
24
|
|
|
22
|
-
页面与接口入口见 [web.ts](../src/web.ts)、[页面模块](../src/web/),任务契约见 [task-contract.ts](../src/task-contract.ts)。提供受管理员认证保护的重启入口;没有通用 MCP 代理、文件浏览器或下载接口。固定结果目录不授予整个 dataDir 的读取能力。
|
|
25
|
+
页面与接口入口见 [web.ts](../src/protocol/http/web.ts)、[页面模块](../src/web/),任务契约见 [task-contract.ts](../src/protocol/task-contract.ts)。提供受管理员认证保护的重启入口;没有通用 MCP 代理、文件浏览器或下载接口。固定结果目录不授予整个 dataDir 的读取能力。
|
|
23
26
|
|
|
24
27
|
## 保存与重启
|
|
25
28
|
|
|
26
|
-
1. 密钥只显示是否设置,原值不回显。不提交即保留;替换和删除必须明确操作。先保存新密钥,再保存引用它的配置。新凭据命名和受保护变量见 [settings.ts](../src/settings.ts)。
|
|
27
|
-
2. 配置先校验并预览影响,再确认保存。校验不调用模型或启动 ACP
|
|
29
|
+
1. 密钥只显示是否设置,原值不回显。不提交即保留;替换和删除必须明确操作。先保存新密钥,再保存引用它的配置。新凭据命名和受保护变量见 [settings.ts](../src/configuration/settings.ts)。
|
|
30
|
+
2. 配置先校验并预览影响,再确认保存。校验不调用模型或启动 ACP,不能证明认证、原生权限或实际执行成功。禁用执行端时显示受影响任务,不自动改派。
|
|
28
31
|
3. 配置与密钥分别原子替换,不是跨文件事务。更换模型端点须使用新的凭据引用,避免中途重启将新密钥交给旧端点。旧凭据不自动清理。
|
|
29
|
-
4. 保存不热更新。检查活动运行及待回答运行后,点击“重启并检查可用性”或执行 `agent-task restart
|
|
32
|
+
4. 保存不热更新。检查活动运行及待回答运行后,点击“重启并检查可用性”或执行 `agent-task restart`。目录切换、原生环境来源及故障处理见[部署说明](deployment.md)。
|
|
30
33
|
5. 只有新进程通过认证健康检查才显示成功。页面清除令牌并给出新地址,重新连接后核对运行配置。请求未确认时先用 CLI status 核查,不删除历史、自动回滚或重放执行来修复。
|
|
31
34
|
|
|
32
|
-
待回答运行也可能持有 ACP
|
|
35
|
+
待回答运行也可能持有 ACP 会话,重启不保证无损续接。执行端未就绪、被禁用或本机路径与任务快照不一致时,旧任务的派发会被拒绝;页面不改写快照、DAG 或执行端指派。需要新任务时由 MCP 调用方处理。
|
|
36
|
+
|
|
37
|
+
重新检测和适配器升级直接作用于当前执行端目录,无需重启;它们不保存启动配置。普通刷新复用已经准备的适配器,只有显式升级才请求更新。升级期间暂停向该执行端派发新工作,完成后可在状态表核对结果。OpenCode 使用原生 ACP,通过原生 CLI 的安装渠道更新。
|
|
33
38
|
|
|
34
|
-
|
|
39
|
+
Web 与 CLI 的旧编辑都会被拒绝,不做自动合并。离线编辑配置须先停服。校验与保存职责见 [settings.ts](../src/configuration/settings.ts)、[配置影响预览](../src/configuration/management.ts)、[settings-contract.ts](../src/protocol/settings-contract.ts)、[配置文件适配](../src/configuration/files.ts)。
|
|
35
40
|
|
|
36
41
|
## 安全与验收
|
|
37
42
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@realqiyan/agent-task",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.1.0-rc.1",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"author": "realqiyan",
|
|
6
6
|
"repository": {
|
|
@@ -21,11 +21,11 @@
|
|
|
21
21
|
"scripts/setup-wizard.mjs",
|
|
22
22
|
"scripts/install-lib.mjs",
|
|
23
23
|
"scripts/service-runner.mjs",
|
|
24
|
-
"scripts/uninstall.mjs",
|
|
25
24
|
"scripts/probe-agent.mjs",
|
|
26
25
|
"scripts/mcp-call.mjs",
|
|
27
26
|
"docs/*.md",
|
|
28
|
-
"
|
|
27
|
+
"docs/adr/*.md",
|
|
28
|
+
"CONTEXT.md",
|
|
29
29
|
"config.example.json",
|
|
30
30
|
"LICENSE"
|
|
31
31
|
],
|
|
@@ -34,14 +34,17 @@
|
|
|
34
34
|
"registry": "https://registry.npmjs.org/",
|
|
35
35
|
"tag": "latest"
|
|
36
36
|
},
|
|
37
|
-
"os": [
|
|
37
|
+
"os": [
|
|
38
|
+
"linux",
|
|
39
|
+
"darwin"
|
|
40
|
+
],
|
|
38
41
|
"type": "module",
|
|
39
42
|
"description": "Durable intelligent task coordination over MCP with ACP executors",
|
|
40
43
|
"engines": {
|
|
41
44
|
"node": ">=22.19.0"
|
|
42
45
|
},
|
|
43
46
|
"scripts": {
|
|
44
|
-
"check": "tsc --noEmit",
|
|
47
|
+
"check": "tsc --noEmit && node scripts/check-boundaries.mjs",
|
|
45
48
|
"build": "rm -rf dist && tsc && node scripts/copy-web.mjs",
|
|
46
49
|
"test": "npm run build && node --test --test-concurrency=1 tests/*.test.mjs",
|
|
47
50
|
"test:e2e": "npm run build && node --test --test-concurrency=1 tests/e2e/deepseek-acp.test.mjs",
|
|
@@ -50,8 +53,7 @@
|
|
|
50
53
|
"setup": "node bin/agent-task.mjs setup",
|
|
51
54
|
"prepack": "npm run build",
|
|
52
55
|
"pack:release": "node scripts/pack-release.mjs",
|
|
53
|
-
"test:install": "node scripts/verify-install.mjs"
|
|
54
|
-
"uninstall:service": "node scripts/uninstall.mjs"
|
|
56
|
+
"test:install": "node scripts/verify-install.mjs"
|
|
55
57
|
},
|
|
56
58
|
"dependencies": {
|
|
57
59
|
"@agentclientprotocol/sdk": "2.0.0",
|
package/scripts/install-lib.mjs
CHANGED
|
@@ -1,29 +1,18 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync, lstatSync, realpathSync, readdirSync, unlinkSync, rmdirSync, openSync, closeSync, fsyncSync, renameSync } from "node:fs";
|
|
1
|
+
import { DatabaseSync } from "../dist/infrastructure/sqlite.js";
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync, lstatSync, realpathSync, unlinkSync, openSync, closeSync, fsyncSync, renameSync } from "node:fs";
|
|
4
3
|
import { dirname, join, resolve, isAbsolute, basename } from "node:path";
|
|
5
4
|
import { homedir } from "node:os";
|
|
6
5
|
import { fileURLToPath } from "node:url";
|
|
7
|
-
import { setTimeout as delay } from "node:timers/promises";
|
|
8
|
-
import { controlService } from "../dist/launcher.js";
|
|
9
6
|
|
|
10
7
|
export const packageRoot = realpathSync(fileURLToPath(new URL("..", import.meta.url)));
|
|
11
8
|
|
|
12
|
-
export const label = "com.agent-task.service";
|
|
13
|
-
export const unit = "agent-task.service";
|
|
14
|
-
export const packages = {
|
|
15
|
-
pi: ["@earendil-works/pi-coding-agent@1.3.0", "pi-acp@0.0.34"],
|
|
16
|
-
codex: ["@agentclientprotocol/codex-acp@2.2.2"],
|
|
17
|
-
claude: ["@zed-industries/claude-agent-acp@0.23.1"],
|
|
18
|
-
opencode: ["opencode-ai@1.19.3"],
|
|
19
|
-
};
|
|
20
9
|
export function argumentsFor(argv) {
|
|
21
|
-
const flags = new Set(["
|
|
10
|
+
const flags = new Set(["check", "resume", "help", "yes", "generate-tokens"]), options = {};
|
|
22
11
|
for (let i = 0; i < argv.length; i++) {
|
|
23
12
|
const key = argv[i].replace(/^--/, "");
|
|
24
13
|
if (!argv[i].startsWith("--") || key in options) throw new Error("Invalid or repeated option");
|
|
25
14
|
if (flags.has(key)) options[key] = true;
|
|
26
|
-
else if (["home", "
|
|
15
|
+
else if (["home", "data-dir", "provider", "model", "port", "token-env", "credentials-file", "base-url", "api", "api-key-env", "context-window", "max-tokens"].includes(key) && argv[i + 1] && !argv[i + 1].startsWith("--")) options[key] = argv[++i];
|
|
27
16
|
else throw new Error("Unknown option or missing value: " + key);
|
|
28
17
|
}
|
|
29
18
|
return options;
|
|
@@ -52,16 +41,10 @@ export function createFile(path, text) {
|
|
|
52
41
|
mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
|
|
53
42
|
writeFileSync(path, text, { flag: "wx", mode: 0o600 });
|
|
54
43
|
}
|
|
55
|
-
export function run(command, args, options = {}) {
|
|
56
|
-
const env = Object.fromEntries(["HOME", "PATH", "LANG", "LC_ALL", "TZ", "TMPDIR"].filter(key => process.env[key] !== undefined).map(key => [key, process.env[key]]));
|
|
57
|
-
const result = spawnSync(command, args, { encoding: "utf8", timeout: 300000, stdio: "pipe", env, ...options });
|
|
58
|
-
if (result.error || result.status !== 0) throw new Error("Command failed (diagnostics withheld; may contain credentials): " + command + " " + args[0]);
|
|
59
|
-
return result.stdout?.trim();
|
|
60
|
-
}
|
|
61
44
|
export function operationLock(root) {
|
|
62
45
|
const db = new DatabaseSync(join(root, "setup-lock.sqlite"));
|
|
63
46
|
try { db.exec("PRAGMA busy_timeout=0; BEGIN EXCLUSIVE"); }
|
|
64
|
-
catch { db.close(); throw new Error("Another setup
|
|
47
|
+
catch { db.close(); throw new Error("Another setup operation owns this installation"); }
|
|
65
48
|
return () => db.close();
|
|
66
49
|
}
|
|
67
50
|
export function readManifest(root) {
|
|
@@ -71,7 +54,7 @@ export function readManifest(root) {
|
|
|
71
54
|
return value;
|
|
72
55
|
}
|
|
73
56
|
export function saveManifest(root, manifest) {
|
|
74
|
-
// Preserve the last committed
|
|
57
|
+
// Preserve the last committed initialization record if a write fails.
|
|
75
58
|
const temporary = join(root, `installation-${process.pid}.tmp`);
|
|
76
59
|
let fd;
|
|
77
60
|
try {
|
|
@@ -88,97 +71,5 @@ export function saveManifest(root, manifest) {
|
|
|
88
71
|
}
|
|
89
72
|
export function runtimeBaseEnv(manifest) {
|
|
90
73
|
// Capture the OS user environment; the runtime isolates private service values before worker creation.
|
|
91
|
-
return { ...process.env, HOME: manifest.home, PATH: [
|
|
92
|
-
}
|
|
93
|
-
const xml = value => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
94
|
-
const systemdQuote = value => '"' + value.replaceAll("\\", "\\\\").replaceAll('"', '\\"').replaceAll("%", "%%").replaceAll("$", "$$") + '"';
|
|
95
|
-
export function serviceDefinition(manifest) {
|
|
96
|
-
const runner = join(manifest.app, "scripts/service-runner.mjs");
|
|
97
|
-
if (manifest.platform === "linux") return {
|
|
98
|
-
path: join(manifest.home, ".config/systemd/user", unit),
|
|
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`,
|
|
100
|
-
};
|
|
101
|
-
return { path: join(manifest.home, "Library/LaunchAgents", label + ".plist"), text:
|
|
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` };
|
|
103
|
-
}
|
|
104
|
-
export function serviceEnv() {
|
|
105
|
-
const keys = ["HOME", "PATH", "LANG", "LC_ALL", "DBUS_SESSION_BUS_ADDRESS"];
|
|
106
|
-
return { ...Object.fromEntries(keys.filter(key => process.env[key] !== undefined).map(key => [key, process.env[key]])),
|
|
107
|
-
XDG_RUNTIME_DIR: process.env.XDG_RUNTIME_DIR ?? `/run/user/${process.getuid()}` };
|
|
108
|
-
}
|
|
109
|
-
export async function checkHealth(home, waitMs = 5000) {
|
|
110
|
-
const deadline = Date.now() + waitMs;
|
|
111
|
-
do {
|
|
112
|
-
try { return await controlService(home, "status"); }
|
|
113
|
-
catch { /* The launcher may still be starting; never log credentials. */ }
|
|
114
|
-
await delay(200);
|
|
115
|
-
} while (Date.now() < deadline);
|
|
116
|
-
throw new Error("Authenticated MCP health check failed; inspect private logs and startup files");
|
|
117
|
-
}
|
|
118
|
-
export function verifyServiceFile(manifest) {
|
|
119
|
-
const definition = serviceDefinition(manifest);
|
|
120
|
-
if (existsSync(definition.path)) {
|
|
121
|
-
privateFile(definition.path);
|
|
122
|
-
if (readFileSync(definition.path, "utf8") !== definition.text) throw new Error("Service file differs; refusing to overwrite or remove it: " + definition.path);
|
|
123
|
-
}
|
|
124
|
-
return definition;
|
|
125
|
-
}
|
|
126
|
-
export function activate(manifest) {
|
|
127
|
-
const definition = verifyServiceFile(manifest);
|
|
128
|
-
if (!existsSync(definition.path)) createFile(definition.path, definition.text);
|
|
129
|
-
if (manifest.platform === "linux") {
|
|
130
|
-
run("systemctl", ["--user", "daemon-reload"], { env: serviceEnv() });
|
|
131
|
-
run("systemctl", ["--user", "enable", "--now", unit], { env: serviceEnv() });
|
|
132
|
-
run("systemctl", ["--user", "is-active", unit], { env: serviceEnv() });
|
|
133
|
-
} else {
|
|
134
|
-
const domain = `gui/${process.getuid()}`;
|
|
135
|
-
const active = spawnSync("launchctl", ["print", domain + "/" + label], { stdio: "ignore" });
|
|
136
|
-
if (active.status !== 0) run("launchctl", ["bootstrap", domain, definition.path]);
|
|
137
|
-
run("launchctl", ["kickstart", domain + "/" + label]);
|
|
138
|
-
run("launchctl", ["print", domain + "/" + label]);
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
export async function deactivate(manifest) {
|
|
142
|
-
const definition = verifyServiceFile(manifest);
|
|
143
|
-
if (!existsSync(definition.path)) {
|
|
144
|
-
if (manifest.serviceInstalled) throw new Error("Managed service file is missing; verify it is unloaded before cleanup");
|
|
145
|
-
return;
|
|
146
|
-
}
|
|
147
|
-
if (manifest.platform === "linux") {
|
|
148
|
-
run("systemctl", ["--user", "disable", "--now", unit], { env: serviceEnv() });
|
|
149
|
-
// Do not remove code while the service is still active or deactivating.
|
|
150
|
-
const state = run("systemctl", ["--user", "show", "--property=ActiveState", "--value", unit], { env: serviceEnv() });
|
|
151
|
-
if (!["inactive", "failed"].includes(state)) throw new Error("Service has not stopped");
|
|
152
|
-
} else {
|
|
153
|
-
const target = `gui/${process.getuid()}/${label}`;
|
|
154
|
-
const active = spawnSync("launchctl", ["print", target], { stdio: "ignore" });
|
|
155
|
-
if (active.status === 0) run("launchctl", ["bootout", target]);
|
|
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
|
-
}
|
|
162
|
-
}
|
|
163
|
-
unlinkSync(definition.path);
|
|
164
|
-
if (manifest.platform === "linux") run("systemctl", ["--user", "daemon-reload"], { env: serviceEnv() });
|
|
165
|
-
}
|
|
166
|
-
export function inventory(root, relative) {
|
|
167
|
-
const path = join(root, relative), stat = lstatSync(path);
|
|
168
|
-
if (!stat.isDirectory() || stat.isSymbolicLink()) return [relative];
|
|
169
|
-
return readdirSync(path).flatMap(name => inventory(root, join(relative, name))).concat(relative);
|
|
170
|
-
}
|
|
171
|
-
export function removeInventory(root, files) {
|
|
172
|
-
for (const relative of files) {
|
|
173
|
-
if (!/^components(\/|$)/.test(relative) || relative.split("/").includes("..")) throw new Error("Invalid component inventory");
|
|
174
|
-
const path = join(root, relative);
|
|
175
|
-
// Never follow a directory replaced by a symlink outside the installation.
|
|
176
|
-
try {
|
|
177
|
-
if (realpathSync(dirname(path)) !== dirname(path)) throw new Error("Component parent path changed; cleanup stopped");
|
|
178
|
-
const stat = lstatSync(path);
|
|
179
|
-
if (stat.isDirectory() && !stat.isSymbolicLink()) {
|
|
180
|
-
try { rmdirSync(path); } catch (error) { if (error.code !== "ENOTEMPTY") throw error; }
|
|
181
|
-
} else unlinkSync(path);
|
|
182
|
-
} catch (error) { if (error.code !== "ENOENT") throw error; }
|
|
183
|
-
}
|
|
74
|
+
return { ...process.env, HOME: manifest.home, PATH: [process.env.PATH ?? "", dirname(manifest.node), "/opt/homebrew/bin", "/usr/local/bin", "/usr/bin", "/bin"].join(":"), LANG: process.env.LANG ?? "en_US.UTF-8" };
|
|
184
75
|
}
|
package/scripts/mcp-call.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
2
2
|
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
3
|
-
import { version } from "../dist/version.js";
|
|
3
|
+
import { version } from "../dist/infrastructure/version.js";
|
|
4
4
|
const [tool, input = "{}"] = process.argv.slice(2);
|
|
5
5
|
if (!tool || !process.env.AGENT_TASK_TOKEN) {
|
|
6
6
|
console.error("Usage: AGENT_TASK_TOKEN=... node scripts/mcp-call.mjs <tool|--list> '[JSON arguments]'\nAGENT_TASK_URL defaults to http://127.0.0.1:8765/mcp");
|
package/scripts/probe-agent.mjs
CHANGED
|
@@ -2,21 +2,23 @@
|
|
|
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 } from "../dist/
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
5
|
+
import { loadConfig, startupPaths } from "../dist/configuration/files.js";
|
|
6
|
+
import { executorEnvironment } from "../dist/executors/environment.js";
|
|
7
|
+
import { executorLaunch } from "../dist/integrations/acp/runtime.js";
|
|
8
|
+
import { LocalExecutors } from "../dist/integrations/acp/discovery.js";
|
|
9
|
+
import { readEnvironment } from "../dist/configuration/files.js";
|
|
8
10
|
import { join } from "node:path";
|
|
9
|
-
import { terminateGroup } from "../dist/acp.js";
|
|
10
|
-
import { version } from "../dist/version.js";
|
|
11
|
-
const [
|
|
12
|
-
if (!
|
|
13
|
-
const config = loadConfig(
|
|
14
|
-
const executor = config.executors.find(value => value.id === id);
|
|
15
|
-
if (!executor) throw new Error("Executor not found");
|
|
11
|
+
import { terminateGroup } from "../dist/integrations/acp/connection.js";
|
|
12
|
+
import { version } from "../dist/infrastructure/version.js";
|
|
13
|
+
const [home, id] = process.argv.slice(2);
|
|
14
|
+
if (!home || !id) { console.error("Usage: node scripts/probe-agent.mjs <installation-home> <pi|codex|claude|opencode>"); process.exit(2); }
|
|
15
|
+
const config = loadConfig(startupPaths(home).configPath);
|
|
16
16
|
const env = executorEnvironment(process.env, readEnvironment(join(config.dataDir, "environment.json")), config);
|
|
17
|
-
const
|
|
17
|
+
const executor = new LocalExecutors(join(home, "adapters"), env).discover().executors.find(value => value.id === id);
|
|
18
|
+
if (!executor || config.disabledExecutors.includes(executor.type)) throw new Error("Executor unavailable; connect MCP to prepare its adapter first");
|
|
19
|
+
const launch = executorLaunch(executor, env);
|
|
18
20
|
const child = spawn(launch.command, launch.args, {
|
|
19
|
-
cwd:
|
|
21
|
+
cwd: process.cwd(), env: launch.env, detached: true, stdio: ["pipe", "pipe", "pipe"],
|
|
20
22
|
});
|
|
21
23
|
child.stderr.resume();
|
|
22
24
|
let connection;
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import { readManifest, runtimeBaseEnv } from "./install-lib.mjs";
|
|
2
|
-
import { launch } from "../dist/launcher.js";
|
|
2
|
+
import { launch } from "../dist/application/launcher.js";
|
|
3
3
|
|
|
4
4
|
process.umask(0o077);
|
|
5
5
|
const root = process.argv[2];
|
|
6
6
|
if (!root || process.argv.length !== 3) throw new Error("Expected installation home");
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
try {
|
|
8
|
+
const manifest = readManifest(root);
|
|
9
|
+
if (manifest.phase !== "ready") throw new Error("Installation is not ready; run agent-task setup --resume");
|
|
10
|
+
await launch(root, runtimeBaseEnv(manifest));
|
|
11
|
+
} catch (error) {
|
|
12
|
+
console.error(error.message);
|
|
13
|
+
process.exitCode = 1;
|
|
14
|
+
}
|
package/scripts/setup-wizard.mjs
CHANGED
|
@@ -1,29 +1,51 @@
|
|
|
1
1
|
import { createInterface } from "node:readline/promises";
|
|
2
|
+
import { Writable } from "node:stream";
|
|
3
|
+
import { join } from "node:path";
|
|
2
4
|
|
|
5
|
+
async function secret(question) {
|
|
6
|
+
let muted = false;
|
|
7
|
+
const output = new Writable({ write(chunk, _encoding, callback) { if (!muted) process.stderr.write(chunk); callback(); } });
|
|
8
|
+
const input = createInterface({ input: process.stdin, output, terminal: true });
|
|
9
|
+
try {
|
|
10
|
+
process.stderr.write(question + ": "); muted = true;
|
|
11
|
+
return (await input.question("")).trim();
|
|
12
|
+
} finally { muted = false; process.stderr.write("\n"); input.close(); output.end(); }
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Terminal collection only; setup validates and publishes the settings. */
|
|
3
16
|
export async function setupWizard(options, root) {
|
|
4
|
-
if (!process.stdin.isTTY || !process.stdout.isTTY) throw new Error("
|
|
17
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) throw new Error("Setup requires a terminal or explicit --home --data-dir --port --generate-tokens/--credentials-file and --yes");
|
|
5
18
|
const input = createInterface({ input: process.stdin, output: process.stdout });
|
|
6
19
|
const abort = new AbortController();
|
|
7
20
|
input.on("SIGINT", () => abort.abort());
|
|
8
|
-
const ask = async (question, fallback) => (await input.question(`${question}${fallback ? " [" + fallback + "]" : ""}: `, { signal: abort.signal })).trim() || fallback;
|
|
21
|
+
const ask = async (question, fallback = "") => (await input.question(`${question}${fallback ? " [" + fallback + "]" : ""}: `, { signal: abort.signal })).trim() || fallback;
|
|
22
|
+
let needsKey = false;
|
|
9
23
|
try {
|
|
10
|
-
|
|
11
|
-
options
|
|
12
|
-
options.
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
options.
|
|
19
|
-
if (
|
|
20
|
-
|
|
21
|
-
if (
|
|
22
|
-
|
|
24
|
+
options.home = await ask("Private installation directory", options.home ?? root);
|
|
25
|
+
options["data-dir"] = await ask("Private data directory", options["data-dir"] ?? join(options.home, "data"));
|
|
26
|
+
options.port = await ask("Loopback HTTP port (0 assigns an available port)", options.port ?? "8765");
|
|
27
|
+
options["token-env"] = await ask("MCP token variable name", options["token-env"] ?? "AGENT_TASK_TOKEN");
|
|
28
|
+
const tokens = await ask("Service/admin tokens (generate/file)", options["credentials-file"] ? "file" : "generate");
|
|
29
|
+
if (tokens === "generate") options["generate-tokens"] = true;
|
|
30
|
+
else if (tokens === "file") options["credentials-file"] = await ask("Private credentials JSON file", options["credentials-file"]);
|
|
31
|
+
else throw new Error("Choose generate or file for tokens");
|
|
32
|
+
options.provider = await ask("Coordinator provider (none/openai/anthropic/deepseek/openai-compatible)", options.provider ?? "none");
|
|
33
|
+
if (options.provider !== "none") {
|
|
34
|
+
options.model = await ask("Coordinator model ID", options.model);
|
|
35
|
+
if (options.provider === "openai-compatible") {
|
|
36
|
+
options["base-url"] = await ask("Model endpoint URL", options["base-url"]);
|
|
37
|
+
options.api = await ask("API (openai-completions/openai-responses)", options.api ?? "openai-completions");
|
|
38
|
+
options["api-key-env"] = await ask("Coordinator credential name", options["api-key-env"] ?? "COORDINATOR_API_KEY");
|
|
39
|
+
options["context-window"] = await ask("Model context window", options["context-window"]);
|
|
40
|
+
options["max-tokens"] = await ask("Maximum output tokens", options["max-tokens"]);
|
|
41
|
+
}
|
|
42
|
+
needsKey = !options["credentials-file"];
|
|
23
43
|
}
|
|
24
|
-
if (await ask("
|
|
44
|
+
if (await ask("Confirm directories, port, token configuration and coordinator? (yes/no)", "no") !== "yes") throw new Error("Setup cancelled; no configuration written");
|
|
45
|
+
options.yes = true;
|
|
25
46
|
} catch (error) {
|
|
26
47
|
if (abort.signal.aborted) throw new Error("Setup cancelled; no configuration written");
|
|
27
48
|
throw error;
|
|
28
49
|
} finally { input.close(); }
|
|
50
|
+
if (needsKey) options.coordinatorKey = await secret("Coordinator API key (hidden)");
|
|
29
51
|
}
|