@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.
Files changed (52) hide show
  1. package/CONTEXT.md +17 -0
  2. package/README.md +28 -6
  3. package/bin/agent-task.mjs +18 -6
  4. package/config.example.json +1 -1
  5. package/dist/application/launcher.js +30 -16
  6. package/dist/application/main.js +5 -2
  7. package/dist/application/mcp-entry.js +71 -0
  8. package/dist/application/task-runtime.js +3 -3
  9. package/dist/configuration/contract.js +2 -2
  10. package/dist/configuration/files.js +2 -3
  11. package/dist/configuration/management.js +1 -1
  12. package/dist/configuration/policy.js +3 -8
  13. package/dist/configuration/settings.js +10 -33
  14. package/dist/executors/ports.js +2 -0
  15. package/dist/executors/registry.js +117 -15
  16. package/dist/executors/validation.js +4 -5
  17. package/dist/infrastructure/lock.js +25 -13
  18. package/dist/integrations/acp/connection.js +5 -5
  19. package/dist/integrations/acp/discovery.js +271 -0
  20. package/dist/integrations/acp/runtime.js +38 -23
  21. package/dist/integrations/acp/worker.js +5 -3
  22. package/dist/integrations/local-service/control.js +36 -11
  23. package/dist/protocol/cli/commands.js +41 -46
  24. package/dist/protocol/http/server.js +8 -0
  25. package/dist/protocol/http/web.js +7 -1
  26. package/dist/protocol/mcp.js +3 -3
  27. package/dist/protocol/settings-contract.js +4 -7
  28. package/dist/protocol/stdio.js +71 -0
  29. package/dist/protocol/task-contract.js +1 -1
  30. package/dist/tasks/definitions.js +8 -5
  31. package/dist/tasks/runs.js +2 -2
  32. package/dist/tasks/storage/records.js +2 -2
  33. package/dist/tasks/storage/store.js +5 -4
  34. package/dist/tasks/supervisor.js +1 -1
  35. package/dist/web/settings-view.js +43 -37
  36. package/docs/adr/0001-task-request-scope.md +3 -0
  37. package/docs/adr/0002-mcp-workspace-source.md +3 -0
  38. package/docs/adr/0003-adapter-installation.md +3 -0
  39. package/docs/architecture-guidelines.md +2 -2
  40. package/docs/cli.md +12 -4
  41. package/docs/deployment.md +52 -36
  42. package/docs/design-proposal.md +3 -0
  43. package/docs/release.md +5 -5
  44. package/docs/technical-design.md +5 -3
  45. package/docs/validation.md +51 -9
  46. package/docs/web-console.md +7 -4
  47. package/package.json +3 -1
  48. package/scripts/install-lib.mjs +5 -23
  49. package/scripts/probe-agent.mjs +10 -9
  50. package/scripts/service-runner.mjs +8 -3
  51. package/scripts/setup-wizard.mjs +38 -11
  52. 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, packages, createFile, json, run, operationLock, readManifest, saveManifest,
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 { readEnvironment } from "../dist/configuration/files.js";
12
- import { executorEnvironment } from "../dist/executors/environment.js";
13
- import { executorCommand } from "../dist/integrations/acp/runtime.js";
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]\nInteractive on a terminal for a new installation. Noninteractive: --executors pi,codex,claude,opencode|none --cwd ABSOLUTE_DIR [--provider openai|anthropic|deepseek --model ID] [--port 8765]\nExisting installation: --check [--probe] | --resume. No credentials accepted on the command line. Existing configuration is never replaced.");
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
- if (options.probe && !options.check) throw new Error("--probe requires --check");
24
- const root = installRoot(options), manifestPath = join(root, "installation.json");
25
- if (!existsSync(manifestPath) && !options.check && !options.resume && (!options.executors || !options.cwd)) await setupWizard(options, root);
26
- if (!existsSync(root)) mkdirSync(root, { recursive: true, mode: 0o700 });
27
- if (realpathSync(root) !== root || lstatSync(root).uid !== process.getuid() || (lstatSync(root).mode & 0o077)) throw new Error("Installation home must be canonical, owned and private");
28
- unlock = operationLock(root);
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 (["executors", "cwd", "provider", "model", "port"].some(key => options[key] !== undefined)) throw new Error("Installation already exists; initialization options cannot replace existing configuration");
33
- if (manifest.phase !== "ready" && !options.resume) throw new Error("Incomplete components; use --resume explicitly (configuration will be preserved)");
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.probe || options.resume) throw new Error("No installation to check/resume");
36
- if (!options.executors || !options.cwd) throw new Error("Select --executors pi,codex,claude,opencode|none and --cwd ABSOLUTE_DIR");
37
- const selected = options.executors === "none" ? [] : options.executors.split(",");
38
- if (selected.some(id => !Object.hasOwn(packages, id)) || new Set(selected).size !== selected.length) throw new Error("Invalid executor selection");
39
- if (!options.cwd.startsWith("/") || !lstatSync(realpathSync(options.cwd)).isDirectory()) throw new Error("cwd must be an existing absolute directory");
40
- const cwd = realpathSync(options.cwd);
41
- if ([root, packageRoot].some(path => cwd === path || cwd.startsWith(path + "/"))) throw new Error("Workspace must be outside the installation home and npm package");
42
- if (Boolean(options.provider) !== Boolean(options.model) || (options.provider && !["openai", "anthropic", "deepseek"].includes(options.provider))) throw new Error("Specify a supported --provider together with --model");
43
- const port = Number(options.port ?? 8765);
44
- if (!Number.isInteger(port) || port < 1 || port > 65535) throw new Error("Invalid port");
45
- // Reject unowned artifacts. This script never adopts or clears an existing installation.
46
- for (const name of ["components", "launch.json", "data"]) {
47
- if (existsSync(join(root, name))) throw new Error("Existing unowned installation artifact: " + name);
48
- }
49
- manifest = { project: "agent-task", root, home: homedir(), platform: process.platform, node: process.execPath, app: packageRoot,
50
- selected, cwd, port, coordinator: options.provider ? { provider: options.provider, model: options.model } : undefined,
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
- try {
63
- mkdirSync(components, { recursive: true, mode: 0o700 });
64
- if (!existsSync(join(components, "package.json"))) createFile(join(components, "package.json"), JSON.stringify({ private: true }));
65
- const dependencies = manifest.selected.flatMap(id => packages[id]);
66
- if (dependencies.length) {
67
- console.log("Installing selected ACP executors...");
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
- const paths = startupPaths(root), env = runtimeBaseEnv(manifest);
92
- // Use the same native launch environment; private values are read separately, never passed to probes.
93
- run(manifest.node, ["--input-type=module", "-e", `import {loadConfig} from ${JSON.stringify(pathToFileURL(join(app, "dist/configuration/files.js")).href)}; loadConfig(process.argv[1]);`, paths.configPath], { env });
94
- const config = json(paths.configPath);
95
- for (const id of manifest.selected) {
96
- const cliEnv = executorEnvironment(env, readEnvironment(paths.environmentPath), config);
97
- executorCommand(id, cliEnv); // No native process is launched without an explicit --probe.
98
- }
99
- const report = { platform: process.platform, dependencies: "ready", configuration: "valid",
100
- coordinator: config.coordinator ? "configured; authentication not verified" : "not configured; task admission disabled",
101
- executors: config.executors.map(executor => ({ id: executor.id, authorized: executor.unattendedApproved,
102
- handshake: "not checked", execution: "not verified by setup" })) };
103
- if (options.probe) {
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
- console.log(JSON.stringify(report, null, 2));
110
- if (!options.check) {
111
- console.log(`Config: ${paths.configPath}\nCredentials: ${paths.environmentPath}\nConsole: http://127.0.0.1:${config.port}/admin/\nSign in with AGENT_TASK_ADMIN_TOKEN from the credentials file.`);
112
- if (config.executors.length) console.log("Prepare native executor authentication and review permissions before approving unattended execution.");
113
- console.log(`Start: agent-task start --home '${root.replaceAll("'", "'\"'\"'")}'`);
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.message); process.exitCode = 1;
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?.(); }