factory-ai 1.3.0 → 1.5.0
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/CHANGELOG.md +24 -4
- package/Dockerfile.worker +1 -1
- package/README.md +10 -2
- package/ROADMAP.md +0 -1
- package/bin/factory +130 -3
- package/bootstrap/agent-factory-worker.service +3 -3
- package/bootstrap/auto-update.sh +20 -12
- package/bootstrap/deploy-runtime.sh +7 -3
- package/bootstrap/factory-ai-container-firewall.service +14 -0
- package/bootstrap/factory-ai-ollama.service +16 -0
- package/bootstrap/factory-ai-qdrant.service +16 -0
- package/bootstrap/factory-ai-snapshot.service +1 -0
- package/bootstrap/factory-ai-watchdog.service +21 -0
- package/bootstrap/factory-ai-watchdog.timer +11 -0
- package/bootstrap/setup.sh +33 -8
- package/cmd/factory-ui/main.go +455 -0
- package/go.mod +39 -0
- package/go.sum +82 -0
- package/package.json +7 -3
- package/scripts/test.js +7 -0
- package/src/acp-adapter.js +26 -0
- package/src/acp-cli.js +16 -0
- package/src/activity.js +96 -0
- package/src/agent-executor.js +47 -3
- package/src/agent-runner.js +34 -12
- package/src/approval-policy.js +12 -0
- package/src/azure-harness.js +47 -14
- package/src/bedrock-harness.js +45 -14
- package/src/config.js +13 -0
- package/src/container-runner.js +65 -12
- package/src/context-compaction.js +16 -0
- package/src/control-plane.js +77 -16
- package/src/control-service.js +6 -1
- package/src/dashboard.js +25 -6
- package/src/extension-cli.js +11 -0
- package/src/extension-manifest.js +76 -0
- package/src/hooks.js +62 -0
- package/src/instructions.js +45 -0
- package/src/mcp-tools.js +19 -3
- package/src/objective-status.js +16 -0
- package/src/operator-logs.js +24 -0
- package/src/operator.js +34 -10
- package/src/process.js +3 -3
- package/src/release-gate.js +1 -0
- package/src/release-service.js +4 -1
- package/src/release.js +1 -1
- package/src/repo-map.js +128 -0
- package/src/reporter.js +12 -1
- package/src/retriever.js +154 -0
- package/src/routing.js +11 -1
- package/src/scanner-suite.js +4 -3
- package/src/setup-menu.js +3 -1
- package/src/snapshot.js +16 -0
- package/src/task-entry.js +28 -6
- package/src/task-graph.js +0 -5
- package/src/telegram-service.js +10 -4
- package/src/telegram.js +7 -3
- package/src/telemetry.js +173 -0
- package/src/tui.js +31 -10
- package/src/validation.js +41 -9
- package/src/watchdog.js +62 -0
- package/src/worker.js +34 -3
- package/src/workspace-tools.js +13 -3
- package/src/workspace.js +22 -2
- package/templates/AGENTS.md +15 -0
package/src/workspace-tools.js
CHANGED
|
@@ -5,6 +5,7 @@ import { run } from "./process.js";
|
|
|
5
5
|
|
|
6
6
|
const ALLOWED_COMMANDS = new Set(["git", "ls", "node", "npm", "npx", "pwd", "rg"]);
|
|
7
7
|
const DENIED_GIT_OPERATIONS = new Set(["credential", "push", "remote"]);
|
|
8
|
+
const READ_ONLY_GIT_OPERATIONS = new Set(["diff", "grep", "log", "ls-files", "rev-parse", "show", "status"]);
|
|
8
9
|
|
|
9
10
|
function lexicalPath(root, requested) {
|
|
10
11
|
const target = path.resolve(root, requested);
|
|
@@ -43,10 +44,11 @@ async function walk(directory, root, output, limit) {
|
|
|
43
44
|
}
|
|
44
45
|
}
|
|
45
46
|
|
|
46
|
-
export function createWorkspaceTools(rootInput, { execute = run } = {}) {
|
|
47
|
+
export function createWorkspaceTools(rootInput, { execute = run, mutable = true, allowTests = false } = {}) {
|
|
47
48
|
const root = realpathSync(path.resolve(rootInput));
|
|
48
|
-
|
|
49
|
+
const tools = {
|
|
49
50
|
read_file: {
|
|
51
|
+
parallelSafe: true,
|
|
50
52
|
description: "Read a bounded UTF-8 line range inside the assigned workspace. Use offsetLine/limitLines instead of rereading large files.",
|
|
51
53
|
parameters: {
|
|
52
54
|
type: "object",
|
|
@@ -69,6 +71,7 @@ export function createWorkspaceTools(rootInput, { execute = run } = {}) {
|
|
|
69
71
|
},
|
|
70
72
|
},
|
|
71
73
|
write_file: {
|
|
74
|
+
parallelSafe: false,
|
|
72
75
|
description: "Atomically replace a UTF-8 file inside the assigned workspace.",
|
|
73
76
|
parameters: {
|
|
74
77
|
type: "object",
|
|
@@ -86,6 +89,7 @@ export function createWorkspaceTools(rootInput, { execute = run } = {}) {
|
|
|
86
89
|
},
|
|
87
90
|
},
|
|
88
91
|
list_files: {
|
|
92
|
+
parallelSafe: true,
|
|
89
93
|
description: "List files recursively inside the assigned workspace.",
|
|
90
94
|
parameters: {
|
|
91
95
|
type: "object",
|
|
@@ -101,6 +105,7 @@ export function createWorkspaceTools(rootInput, { execute = run } = {}) {
|
|
|
101
105
|
},
|
|
102
106
|
},
|
|
103
107
|
run_command: {
|
|
108
|
+
parallelSafe: false,
|
|
104
109
|
description: "Run an allowlisted noninteractive development command in the assigned workspace.",
|
|
105
110
|
parameters: {
|
|
106
111
|
type: "object",
|
|
@@ -113,7 +118,10 @@ export function createWorkspaceTools(rootInput, { execute = run } = {}) {
|
|
|
113
118
|
},
|
|
114
119
|
execute: async ({ command, args }) => {
|
|
115
120
|
if (!ALLOWED_COMMANDS.has(command)) throw new Error(`Command not allowed: ${command}`);
|
|
116
|
-
|
|
121
|
+
const testCommand = allowTests && ((command === "npm" && ["test", "run"].includes(args[0])) || (command === "node" && args[0] === "--test") || (command === "npx" && args[0] === "--no-install"));
|
|
122
|
+
if (!mutable && !testCommand && !["git", "ls", "pwd", "rg"].includes(command)) throw new Error(`Command not allowed for read-only role: ${command}`);
|
|
123
|
+
if (command === "git" && (args.some((item) => DENIED_GIT_OPERATIONS.has(item)) || args.some((item) => ["-c", "--config-env", "--exec-path"].includes(item)))) throw new Error("Git operation not allowed");
|
|
124
|
+
if (!mutable && command === "git" && (args[0]?.startsWith("-") || !READ_ONLY_GIT_OPERATIONS.has(args[0]))) throw new Error(`Git operation not allowed for read-only role: ${args[0]}`);
|
|
117
125
|
if (command === "npx" && args[0] !== "--no-install") throw new Error("npx requires --no-install");
|
|
118
126
|
const result = await execute(command, args, {
|
|
119
127
|
cwd: root,
|
|
@@ -127,4 +135,6 @@ export function createWorkspaceTools(rootInput, { execute = run } = {}) {
|
|
|
127
135
|
},
|
|
128
136
|
},
|
|
129
137
|
};
|
|
138
|
+
if (!mutable) delete tools.write_file;
|
|
139
|
+
return tools;
|
|
130
140
|
}
|
package/src/workspace.js
CHANGED
|
@@ -45,10 +45,18 @@ export class WorkspaceManager {
|
|
|
45
45
|
|
|
46
46
|
async prepareTask(objective, task, dependencyCommits = []) {
|
|
47
47
|
const directory = this.taskDirectory(objective.id, task.id);
|
|
48
|
-
|
|
48
|
+
const branch = `factory-ai/${objective.id}/${task.id}`;
|
|
49
|
+
if (await exists(directory)) {
|
|
50
|
+
const current = (await run("git", ["-C", directory, "branch", "--show-current"])).stdout.trim();
|
|
51
|
+
if (current !== branch) throw new Error(`Existing task workspace is on unexpected branch: ${current}`);
|
|
52
|
+
for (const commit of dependencyCommits) {
|
|
53
|
+
const ancestor = await run("git", ["-C", directory, "merge-base", "--is-ancestor", commit, "HEAD"], { allowExitCodes: [0, 1] });
|
|
54
|
+
if (ancestor.code !== 0) throw new Error(`Existing task workspace is missing dependency commit: ${commit}`);
|
|
55
|
+
}
|
|
56
|
+
return directory;
|
|
57
|
+
}
|
|
49
58
|
await mkdir(path.dirname(directory), { recursive: true, mode: 0o750 });
|
|
50
59
|
const base = dependencyCommits[0] ?? `origin/${objective.baseBranch}`;
|
|
51
|
-
const branch = `factory-ai/${objective.id}/${task.id}`;
|
|
52
60
|
await run("git", ["clone", objective.repository, directory], { timeoutMs: this.timeoutMs });
|
|
53
61
|
await run("git", ["-C", directory, "config", "user.name", "Factory AI"]);
|
|
54
62
|
await run("git", ["-C", directory, "config", "user.email", "factory-ai@localhost"]);
|
|
@@ -60,6 +68,13 @@ export class WorkspaceManager {
|
|
|
60
68
|
return directory;
|
|
61
69
|
}
|
|
62
70
|
|
|
71
|
+
async recoveryContext(directory) {
|
|
72
|
+
const status = (await run("git", ["-C", directory, "status", "--short"], { maxOutputBytes: 50_000 })).stdout.trim();
|
|
73
|
+
if (!status) return "";
|
|
74
|
+
const summary = (await run("git", ["-C", directory, "diff", "--stat"], { maxOutputBytes: 50_000 })).stdout.trim();
|
|
75
|
+
return `RECOVERED DURABLE WORKTREE CHECKPOINT\nA previous attempt changed these files. Inspect and continue rather than repeating completed work.\n${status}${summary ? `\n\n${summary}` : ""}`;
|
|
76
|
+
}
|
|
77
|
+
|
|
63
78
|
async checkpoint(directory, objective, task) {
|
|
64
79
|
await run("git", ["-C", directory, "add", "-A"]);
|
|
65
80
|
const diff = await run("git", ["-C", directory, "diff", "--cached", "--quiet"], { allowExitCodes: [0, 1] });
|
|
@@ -75,4 +90,9 @@ export class WorkspaceManager {
|
|
|
75
90
|
return { commit, branch };
|
|
76
91
|
}
|
|
77
92
|
|
|
93
|
+
async reference(directory, objective, task) {
|
|
94
|
+
const { stdout } = await run("git", ["-C", directory, "rev-parse", "HEAD"]);
|
|
95
|
+
return { commit: stdout.trim(), branch: `factory-ai/${objective.id}/${task.id}` };
|
|
96
|
+
}
|
|
97
|
+
|
|
78
98
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Project Agent Instructions
|
|
2
|
+
|
|
3
|
+
## Working Agreement
|
|
4
|
+
|
|
5
|
+
- Make the smallest correct change that satisfies the objective.
|
|
6
|
+
- Read existing code and tests before editing. Preserve established architecture and style.
|
|
7
|
+
- Never read, print, persist, or commit credentials or local environment files.
|
|
8
|
+
- Keep changes inside this repository. Do not push, deploy, or mutate external systems.
|
|
9
|
+
- Add or update focused tests for behavior changes.
|
|
10
|
+
- Run the project's documented format, typecheck, lint, test, and build gates before claiming completion.
|
|
11
|
+
- Report commands run, results, remaining risks, and any assumptions.
|
|
12
|
+
|
|
13
|
+
## Project Commands
|
|
14
|
+
|
|
15
|
+
Record project-specific commands and constraints in `.agent-factory/commands.md` and `.agent-factory/project.md`.
|