@realqiyan/agent-task 0.0.1-rc.3 → 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 +28 -6
- package/bin/agent-task.mjs +18 -6
- package/config.example.json +1 -1
- package/dist/application/launcher.js +30 -16
- package/dist/application/main.js +5 -2
- package/dist/application/mcp-entry.js +71 -0
- package/dist/application/task-runtime.js +3 -3
- package/dist/configuration/contract.js +2 -2
- package/dist/configuration/files.js +2 -3
- package/dist/configuration/management.js +1 -1
- package/dist/configuration/policy.js +3 -8
- package/dist/configuration/settings.js +10 -33
- package/dist/executors/ports.js +2 -0
- package/dist/executors/registry.js +117 -15
- package/dist/executors/validation.js +4 -5
- package/dist/infrastructure/lock.js +25 -13
- package/dist/integrations/acp/connection.js +5 -5
- package/dist/integrations/acp/discovery.js +271 -0
- package/dist/integrations/acp/runtime.js +38 -23
- package/dist/integrations/acp/worker.js +5 -3
- package/dist/integrations/local-service/control.js +36 -11
- package/dist/protocol/cli/commands.js +41 -46
- package/dist/protocol/http/server.js +8 -0
- package/dist/protocol/http/web.js +7 -1
- package/dist/protocol/mcp.js +3 -3
- package/dist/protocol/settings-contract.js +4 -7
- package/dist/protocol/stdio.js +71 -0
- package/dist/protocol/task-contract.js +1 -1
- package/dist/tasks/definitions.js +8 -5
- package/dist/tasks/runs.js +2 -2
- package/dist/tasks/storage/records.js +2 -2
- package/dist/tasks/storage/store.js +5 -4
- package/dist/tasks/supervisor.js +1 -1
- package/dist/web/settings-view.js +43 -37
- 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 +2 -2
- package/docs/cli.md +12 -4
- package/docs/deployment.md +52 -36
- package/docs/design-proposal.md +3 -0
- package/docs/release.md +5 -5
- package/docs/technical-design.md +5 -3
- package/docs/validation.md +51 -9
- package/docs/web-console.md +7 -4
- package/package.json +3 -1
- package/scripts/install-lib.mjs +5 -23
- package/scripts/probe-agent.mjs +10 -9
- package/scripts/service-runner.mjs +8 -3
- package/scripts/setup-wizard.mjs +38 -11
- package/scripts/setup.mjs +69 -95
package/scripts/setup.mjs
CHANGED
|
@@ -1,117 +1,91 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { existsSync, mkdirSync, realpathSync, lstatSync } from "node:fs";
|
|
3
|
-
import { join } from "node:path";
|
|
4
|
-
import { pathToFileURL } from "node:url";
|
|
3
|
+
import { join, isAbsolute } from "node:path";
|
|
5
4
|
import { homedir } from "node:os";
|
|
6
5
|
import { randomBytes } from "node:crypto";
|
|
7
|
-
import { argumentsFor, installRoot,
|
|
8
|
-
runtimeBaseEnv, inventory, packageRoot } from "./install-lib.mjs";
|
|
6
|
+
import { argumentsFor, installRoot, createFile, json, operationLock, readManifest, saveManifest, packageRoot } from "./install-lib.mjs";
|
|
9
7
|
import { setupWizard } from "./setup-wizard.mjs";
|
|
10
|
-
import { startupPaths } from "../dist/configuration/files.js";
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
8
|
+
import { startupPaths, loadConfig, readEnvironment } from "../dist/configuration/files.js";
|
|
9
|
+
import { configSchema } from "../dist/configuration/contract.js";
|
|
10
|
+
import { validateConfig, coordinatorCredentialEnv } from "../dist/configuration/policy.js";
|
|
11
|
+
import { validateServiceEnvironment, parseEnvironment } from "../dist/configuration/environment.js";
|
|
12
|
+
import { checkDataDirectory } from "../dist/infrastructure/private-files.js";
|
|
14
13
|
|
|
15
14
|
process.umask(0o077);
|
|
16
15
|
let unlock;
|
|
17
16
|
try {
|
|
18
17
|
const options = argumentsFor(process.argv.slice(2));
|
|
19
18
|
if (options.help) {
|
|
20
|
-
console.log("Usage: agent-task setup [--home ABSOLUTE_PRIVATE_DIR]\
|
|
19
|
+
console.log("Usage: agent-task setup [--home ABSOLUTE_PRIVATE_DIR]\nNew installation: confirm directories, port, tokens and coordinator in the terminal.\nNoninteractive: --home DIR --data-dir DIR --port NUMBER --generate-tokens|--credentials-file PRIVATE_JSON --yes [--provider openai|anthropic|deepseek|openai-compatible --model ID]\nCompatible provider: --base-url URL --api openai-completions|openai-responses --api-key-env NAME --context-window NUMBER --max-tokens NUMBER\nExisting installation: --check | --resume. Secrets are read from a private JSON file or hidden terminal input, never command arguments. No executor installation during setup.");
|
|
21
20
|
process.exit(0);
|
|
22
21
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
let manifest;
|
|
30
|
-
if (existsSync(manifestPath)) {
|
|
22
|
+
let root = installRoot(options);
|
|
23
|
+
if (!existsSync(join(root, "installation.json")) && !options.check && !options.resume && !options.yes) {
|
|
24
|
+
await setupWizard(options, root); root = installRoot(options);
|
|
25
|
+
}
|
|
26
|
+
let manifest, initialEnvironment;
|
|
27
|
+
if (existsSync(join(root, "installation.json"))) {
|
|
31
28
|
manifest = readManifest(root);
|
|
32
|
-
if (
|
|
33
|
-
if (manifest.phase !== "ready" && !options.resume) throw new Error("Incomplete
|
|
29
|
+
if (Object.keys(options).some(key => !["home", "check", "resume"].includes(key))) throw new Error("Installation exists; use configuration commands instead of initialization flags");
|
|
30
|
+
if (manifest.phase !== "ready" && !options.resume) throw new Error("Incomplete initialization; inspect private files and use --resume explicitly");
|
|
34
31
|
} else {
|
|
35
|
-
if (options.check || options.
|
|
36
|
-
if (!options.
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
if (
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
phase: "installing", files: [] };
|
|
52
|
-
createFile(manifestPath, JSON.stringify(manifest, null, 2) + "\n");
|
|
53
|
-
}
|
|
54
|
-
const app = manifest.app, components = join(root, "components");
|
|
55
|
-
if (!existsSync(join(app, "dist/application/main.js"))) throw new Error("Prebuilt runtime is missing; reinstall the npm package (source contributors: npm run build)");
|
|
56
|
-
if (manifest.phase !== "ready") {
|
|
57
|
-
for (const name of ["components"]) {
|
|
58
|
-
if (!existsSync(join(root, name))) continue;
|
|
59
|
-
const known = new Set(manifest.files);
|
|
60
|
-
if (inventory(root, name).some(file => !known.has(file))) throw new Error("Uninventoried component files exist; refusing to overwrite them during resume");
|
|
32
|
+
if (options.check || options.resume) throw new Error("No installation to check/resume");
|
|
33
|
+
if (!options.yes || !options.home || !options["data-dir"] || options.port === undefined ||
|
|
34
|
+
(!options["generate-tokens"] && !options["credentials-file"])) throw new Error("Confirm initialization with --home, --data-dir, --port, --generate-tokens/--credentials-file and --yes");
|
|
35
|
+
const dataDir = installRoot({ home: options["data-dir"] });
|
|
36
|
+
if (dataDir === root || dataDir === packageRoot || dataDir.startsWith(packageRoot + "/")) throw new Error("Data directory must be separate from the installation root and npm package");
|
|
37
|
+
const coordinator = !options.provider || options.provider === "none" ? undefined : {
|
|
38
|
+
provider: options.provider, model: options.model,
|
|
39
|
+
...(options.provider === "openai-compatible" ? { baseUrl: options["base-url"], api: options.api,
|
|
40
|
+
apiKeyEnv: options["api-key-env"], contextWindow: Number(options["context-window"]), maxTokens: Number(options["max-tokens"]), reasoning: false } : {}),
|
|
41
|
+
};
|
|
42
|
+
const config = validateConfig(configSchema.parse({ dataDir, port: Number(options.port), tokenEnv: options["token-env"] ?? "AGENT_TASK_TOKEN", coordinator, disabledExecutors: [] }));
|
|
43
|
+
initialEnvironment = options["credentials-file"] ? parseEnvironment(JSON.stringify(json(options["credentials-file"]))) : {};
|
|
44
|
+
if (options["generate-tokens"]) {
|
|
45
|
+
if (initialEnvironment[config.tokenEnv] || initialEnvironment.AGENT_TASK_ADMIN_TOKEN) throw new Error("Choose either supplied service/admin tokens or generated tokens");
|
|
46
|
+
initialEnvironment[config.tokenEnv] = randomBytes(32).toString("hex");
|
|
47
|
+
initialEnvironment.AGENT_TASK_ADMIN_TOKEN = randomBytes(32).toString("hex");
|
|
61
48
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
const installArgs = existsSync(join(components, "package-lock.json"))
|
|
69
|
-
? ["ci", "--ignore-scripts", "--no-audit", "--no-fund"]
|
|
70
|
-
: ["install", "--save-exact", "--ignore-scripts", "--no-audit", "--no-fund", ...dependencies];
|
|
71
|
-
run("npm", installArgs, { cwd: components });
|
|
72
|
-
if (manifest.selected.includes("opencode")) {
|
|
73
|
-
// This pinned package requires copying its platform binary. No other lifecycle scripts run.
|
|
74
|
-
run(manifest.node, [join(components, "node_modules/opencode-ai/postinstall.mjs")], { cwd: components });
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
mkdirSync(join(root, "data/results"), { recursive: true, mode: 0o700 });
|
|
78
|
-
const dataDir = join(root, "data");
|
|
79
|
-
if (!existsSync(join(root, "launch.json"))) createFile(join(root, "launch.json"), JSON.stringify({ dataDir }) + "\n");
|
|
80
|
-
if (!existsSync(join(dataDir, "environment.json"))) createFile(join(dataDir, "environment.json"), JSON.stringify({ AGENT_TASK_TOKEN: randomBytes(32).toString("hex"), AGENT_TASK_ADMIN_TOKEN: randomBytes(32).toString("hex") }, null, 2) + "\n");
|
|
81
|
-
if (!existsSync(join(dataDir, "config.json"))) createFile(join(dataDir, "config.json"), JSON.stringify({ dataDir, port: manifest.port,
|
|
82
|
-
tokenEnv: "AGENT_TASK_TOKEN", coordinator: manifest.coordinator,
|
|
83
|
-
executors: manifest.selected.map(id => ({ id, type: id, note: "", cwd: manifest.cwd, unattendedApproved: false })) }, null, 2) + "\n");
|
|
84
|
-
manifest.phase = "ready";
|
|
85
|
-
delete manifest.port; delete manifest.cwd; delete manifest.coordinator;
|
|
86
|
-
} finally {
|
|
87
|
-
manifest.files = ["components"].filter(name => existsSync(join(root, name))).flatMap(name => inventory(root, name));
|
|
88
|
-
saveManifest(root, manifest);
|
|
49
|
+
if (coordinator && options.coordinatorKey) initialEnvironment[coordinatorCredentialEnv(config.coordinator)] = options.coordinatorKey;
|
|
50
|
+
validateServiceEnvironment(config, initialEnvironment);
|
|
51
|
+
if (!initialEnvironment.AGENT_TASK_ADMIN_TOKEN) throw new Error("An independent administrator token is required for initialization");
|
|
52
|
+
if (coordinator && !initialEnvironment[coordinatorCredentialEnv(config.coordinator)]) throw new Error("Coordinator credential is missing; supply it through the private credentials file");
|
|
53
|
+
for (const path of [join(root, "launch.json"), join(dataDir, "config.json"), join(dataDir, "environment.json")]) {
|
|
54
|
+
if (existsSync(path)) throw new Error("Existing unowned initialization file; refusing to overwrite: " + path);
|
|
89
55
|
}
|
|
56
|
+
checkDataDirectory(root); checkDataDirectory(dataDir);
|
|
57
|
+
manifest = { project: "agent-task", root, home: homedir(), platform: process.platform, node: process.execPath,
|
|
58
|
+
app: packageRoot, phase: "initializing", dataDir, config };
|
|
90
59
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
for (const entry of report.executors) {
|
|
105
|
-
try { run(manifest.node, [join(app, "scripts/probe-agent.mjs"), paths.configPath, entry.id], { env, timeout: 20000 }); entry.handshake = "passed (initialize only)"; }
|
|
106
|
-
catch { entry.handshake = "failed; inspect native dependencies/configuration separately"; process.exitCode = 1; }
|
|
60
|
+
mkdirSync(root, { recursive: true, mode: 0o700 });
|
|
61
|
+
if (realpathSync(root) !== root || lstatSync(root).uid !== process.getuid() || (lstatSync(root).mode & 0o077)) throw new Error("Installation home must be canonical, owned and private");
|
|
62
|
+
unlock = operationLock(root);
|
|
63
|
+
if (!existsSync(join(root, "installation.json"))) createFile(join(root, "installation.json"), JSON.stringify(manifest, null, 2) + "\n");
|
|
64
|
+
if (!existsSync(join(packageRoot, "dist/application/main.js"))) throw new Error("Prebuilt runtime missing; reinstall the package");
|
|
65
|
+
if (manifest.phase !== "ready") {
|
|
66
|
+
if (!isAbsolute(manifest.dataDir) || !manifest.config) throw new Error("Incomplete installation record; no repair performed");
|
|
67
|
+
const dataDir = manifest.dataDir;
|
|
68
|
+
mkdirSync(dataDir, { recursive: true, mode: 0o700 }); checkDataDirectory(dataDir);
|
|
69
|
+
const environmentPath = join(dataDir, "environment.json"), configPath = join(dataDir, "config.json");
|
|
70
|
+
if (!existsSync(environmentPath)) {
|
|
71
|
+
if (!initialEnvironment) throw new Error("Initialization has no saved credentials; inspect the private directory before resuming");
|
|
72
|
+
createFile(environmentPath, JSON.stringify(initialEnvironment, null, 2) + "\n");
|
|
107
73
|
}
|
|
74
|
+
if (!existsSync(configPath)) createFile(configPath, JSON.stringify(manifest.config, null, 2) + "\n");
|
|
75
|
+
const config = loadConfig(configPath), environment = readEnvironment(environmentPath);
|
|
76
|
+
if (JSON.stringify(config) !== JSON.stringify(manifest.config)) throw new Error("Saved initialization configuration differs; no repair performed");
|
|
77
|
+
validateServiceEnvironment(config, environment);
|
|
78
|
+
if (!existsSync(join(root, "launch.json"))) createFile(join(root, "launch.json"), JSON.stringify({ dataDir }) + "\n");
|
|
79
|
+
if (startupPaths(root).dataDir !== dataDir) throw new Error("Launch locator differs from initialization");
|
|
80
|
+
manifest.phase = "ready"; delete manifest.config; delete manifest.dataDir;
|
|
81
|
+
saveManifest(root, manifest);
|
|
108
82
|
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
}
|
|
83
|
+
const paths = startupPaths(root), config = loadConfig(paths.configPath);
|
|
84
|
+
validateServiceEnvironment(config, readEnvironment(paths.environmentPath));
|
|
85
|
+
console.log(JSON.stringify({ configuration: "valid", coordinator: config.coordinator ? "configured; authentication not verified" : "not configured",
|
|
86
|
+
configPath: paths.configPath, credentialsPath: paths.environmentPath, executorDiscovery: "on MCP connection" }, null, 2));
|
|
87
|
+
if (!options.check) console.log("Add agent-task with args [mcp] to your MCP client (include --home for a custom installation). Token values are not printed. Use agent-task start for foreground management.");
|
|
115
88
|
} catch (error) {
|
|
116
|
-
console.error(error.
|
|
89
|
+
console.error(error.name === "ZodError" ? "Invalid initialization configuration; check provider, model, directories and port" : error.message);
|
|
90
|
+
process.exitCode = 1;
|
|
117
91
|
} finally { unlock?.(); }
|