factory-ai 1.0.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/AGENTS.md +3 -0
- package/ARCHITECTURE.md +9 -0
- package/CHANGELOG.md +21 -0
- package/CODE_OF_CONDUCT.md +7 -0
- package/CONTRIBUTING.md +17 -0
- package/Dockerfile.worker +19 -0
- package/GOVERNANCE.md +21 -0
- package/HANDOFF.md +23 -0
- package/LICENSE +21 -0
- package/README.md +244 -0
- package/ROADMAP.md +23 -0
- package/RUNBOOK.md +42 -0
- package/SECURITY.md +7 -0
- package/SUPPORT.md +7 -0
- package/bin/factory +271 -0
- package/bootstrap/agent-factory-control.service +32 -0
- package/bootstrap/agent-factory-release.service +25 -0
- package/bootstrap/agent-factory-reporter.service +17 -0
- package/bootstrap/agent-factory-reporter.timer +10 -0
- package/bootstrap/agent-factory-telegram.service +31 -0
- package/bootstrap/agent-factory-worker.service +39 -0
- package/bootstrap/cloud-init.yaml +78 -0
- package/bootstrap/deploy-runtime.sh +74 -0
- package/bootstrap/setup.sh +124 -0
- package/capabilities/autonomous-loop/SKILL.md +10 -0
- package/capabilities/code-search.md +3 -0
- package/capabilities/dependency-security.md +3 -0
- package/capabilities/frontend-verification.md +3 -0
- package/capabilities/goal-management/SKILL.md +10 -0
- package/capabilities/project-context/SKILL.md +10 -0
- package/capabilities/release-checklist.md +3 -0
- package/capabilities/secure-review.md +3 -0
- package/capabilities/systematic-debugging.md +3 -0
- package/capabilities/test-discipline.md +3 -0
- package/capabilities/test-driven-development.md +3 -0
- package/capabilities/token-efficiency.md +3 -0
- package/capabilities/verification-before-completion.md +3 -0
- package/config/agent-instructions.md +8 -0
- package/config/capabilities.json +107 -0
- package/infra/main.bicep +310 -0
- package/package.json +78 -0
- package/src/agent-executor.js +52 -0
- package/src/agent-runner.js +110 -0
- package/src/azure-harness.js +118 -0
- package/src/bedrock-harness.js +52 -0
- package/src/bus.js +20 -0
- package/src/capabilities.js +43 -0
- package/src/ceo.js +67 -0
- package/src/config.js +65 -0
- package/src/container-runner.js +77 -0
- package/src/control-plane.js +142 -0
- package/src/control-service.js +55 -0
- package/src/dashboard.js +180 -0
- package/src/log.js +19 -0
- package/src/mcp-tools.js +53 -0
- package/src/operator.js +57 -0
- package/src/process.js +48 -0
- package/src/project-memory.js +22 -0
- package/src/registry.js +6 -0
- package/src/release-bot.js +36 -0
- package/src/release-gate.js +11 -0
- package/src/release-service.js +41 -0
- package/src/release.js +102 -0
- package/src/reporter.js +49 -0
- package/src/routing.js +34 -0
- package/src/scanner-suite.js +58 -0
- package/src/secrets.js +22 -0
- package/src/setup-menu.js +26 -0
- package/src/state.js +49 -0
- package/src/task-entry.js +19 -0
- package/src/task-graph.js +67 -0
- package/src/telegram-service.js +99 -0
- package/src/telegram.js +31 -0
- package/src/tui.js +130 -0
- package/src/validation.js +74 -0
- package/src/worker.js +77 -0
- package/src/workspace-tools.js +121 -0
- package/src/workspace.js +78 -0
- package/templates/project/architecture.md +3 -0
- package/templates/project/commands.md +3 -0
- package/templates/project/decisions.md +3 -0
- package/templates/project/handoff.md +3 -0
- package/templates/project/project.md +3 -0
- package/templates/project/risks.md +3 -0
package/bin/factory
ADDED
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
CONFIG_FILE=${FACTORY_CONFIG_FILE:-$HOME/.config/factory-ai/config}
|
|
5
|
+
LEGACY_CONFIG_FILE=$HOME/.config/agent-factory/config
|
|
6
|
+
if [[ -f $CONFIG_FILE ]]; then source "$CONFIG_FILE"; elif [[ -f $LEGACY_CONFIG_FILE ]]; then source "$LEGACY_CONFIG_FILE"; fi
|
|
7
|
+
RESOURCE_GROUP=${FACTORY_RESOURCE_GROUP:-factory-ai-rg}
|
|
8
|
+
VM=${FACTORY_VM:-agent-factory-vm}
|
|
9
|
+
NAMESPACE=${FACTORY_SERVICE_BUS:-}
|
|
10
|
+
VAULT=${FACTORY_KEY_VAULT:-}
|
|
11
|
+
|
|
12
|
+
remote() {
|
|
13
|
+
az vm run-command invoke --resource-group "$RESOURCE_GROUP" --name "$VM" \
|
|
14
|
+
--command-id RunShellScript --scripts "$1" --query 'value[0].message' --output tsv
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
usage() {
|
|
18
|
+
printf '%s\n' \
|
|
19
|
+
'Factory AI operator console' \
|
|
20
|
+
'Usage: factory <command>' \
|
|
21
|
+
' setup' \
|
|
22
|
+
' github status | connect ORG | transfer OWNER/REPO ORG' \
|
|
23
|
+
' telegram status | configure | start | stop' \
|
|
24
|
+
' submit <owner/repo> <objective>' \
|
|
25
|
+
' issue <owner/repo> <number>' \
|
|
26
|
+
' ui' \
|
|
27
|
+
' init <local-project-path>' \
|
|
28
|
+
' secret list | set NAME | copy NAME | delete NAME' \
|
|
29
|
+
' dashboard | status | queue | report | logs | doctor' \
|
|
30
|
+
' pause | resume'
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
command=${1:-help}
|
|
34
|
+
shift || true
|
|
35
|
+
case "$command" in
|
|
36
|
+
telegram)
|
|
37
|
+
action=${1:-status}
|
|
38
|
+
case "$action" in
|
|
39
|
+
status) remote 'systemctl status agent-factory-telegram.service --no-pager -n 20' ;;
|
|
40
|
+
start) remote 'systemctl restart agent-factory-telegram.service; systemctl is-active agent-factory-telegram.service' ;;
|
|
41
|
+
stop) remote 'systemctl stop agent-factory-telegram.service' ;;
|
|
42
|
+
configure)
|
|
43
|
+
read -r -s -p 'Telegram bot token from @BotFather: ' telegram_token; printf '\n'
|
|
44
|
+
read -r -p 'Allowed Telegram chat IDs (comma-separated): ' telegram_chat_ids
|
|
45
|
+
[[ $telegram_chat_ids =~ ^-?[0-9]+(,-?[0-9]+)*$ ]] || { printf 'Invalid Telegram chat ID allowlist\n' >&2; exit 2; }
|
|
46
|
+
ip=$(curl -fsS https://api.ipify.org)
|
|
47
|
+
cleanup_telegram_vault() { az keyvault network-rule remove --name "$VAULT" --ip-address "$ip/32" --output none >/dev/null 2>&1 || true; }
|
|
48
|
+
trap cleanup_telegram_vault EXIT
|
|
49
|
+
az keyvault network-rule add --name "$VAULT" --ip-address "$ip/32" --output none
|
|
50
|
+
sleep 5
|
|
51
|
+
az keyvault secret set --vault-name "$VAULT" --name telegram-bot-token --value "$telegram_token" --output none
|
|
52
|
+
az keyvault secret set --vault-name "$VAULT" --name telegram-allowed-chat-ids --value "$telegram_chat_ids" --output none
|
|
53
|
+
unset telegram_token
|
|
54
|
+
remote 'systemctl restart agent-factory-telegram.service; systemctl is-active agent-factory-telegram.service'
|
|
55
|
+
;;
|
|
56
|
+
*) printf 'Unknown telegram action: %s\n' "$action" >&2; exit 2 ;;
|
|
57
|
+
esac
|
|
58
|
+
;;
|
|
59
|
+
issue)
|
|
60
|
+
repo=${1:?repository owner/name required}
|
|
61
|
+
number=${2:?issue number required}
|
|
62
|
+
[[ $repo =~ ^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$ && $number =~ ^[0-9]+$ ]] || { printf 'Invalid repository or issue number\n' >&2; exit 2; }
|
|
63
|
+
issue=$(gh issue view "$number" --repo "$repo" --json title,body,url)
|
|
64
|
+
title=$(jq -r .title <<<"$issue")
|
|
65
|
+
body=$(jq -r .body <<<"$issue")
|
|
66
|
+
url=$(jq -r .url <<<"$issue")
|
|
67
|
+
objective="/goal Resolve GitHub issue #$number: $title"
|
|
68
|
+
objective+=$'\n\n'
|
|
69
|
+
objective+="$body"
|
|
70
|
+
objective+=$'\n\nSource: '
|
|
71
|
+
objective+="$url"
|
|
72
|
+
exec "$0" submit "$repo" "$objective"
|
|
73
|
+
;;
|
|
74
|
+
ui)
|
|
75
|
+
FACTORY_RESOURCE_GROUP="$RESOURCE_GROUP" FACTORY_VM="$VM" FACTORY_SERVICE_BUS="$NAMESPACE" FACTORY_KEY_VAULT="$VAULT" node "$(dirname "$0")/../src/tui.js"
|
|
76
|
+
;;
|
|
77
|
+
setup)
|
|
78
|
+
for dependency in az gh jq curl ssh-keygen node; do command -v "$dependency" >/dev/null || { printf 'Missing dependency: %s\n' "$dependency" >&2; exit 1; }; done
|
|
79
|
+
choices=$(mktemp)
|
|
80
|
+
trap 'rm -f "$choices"' EXIT
|
|
81
|
+
node "$(dirname "$0")/../src/setup-menu.js" "$choices"
|
|
82
|
+
provider=$(jq -r .provider "$choices")
|
|
83
|
+
location=$(jq -r .location "$choices")
|
|
84
|
+
enterprise_org=$(jq -r .githubOrg "$choices")
|
|
85
|
+
aws_region=$(jq -r .awsRegion "$choices")
|
|
86
|
+
bedrock_builder=$(jq -r .bedrockBuilderModel "$choices")
|
|
87
|
+
deploy_now=$(jq -r .deployNow "$choices")
|
|
88
|
+
telegram=$(jq -r .telegram "$choices")
|
|
89
|
+
az account show --query '{subscription:name,state:state}' --output table
|
|
90
|
+
gh auth status
|
|
91
|
+
az group show --name "$RESOURCE_GROUP" --output none 2>/dev/null || az group create --name "$RESOURCE_GROUP" --location "$location" --output none
|
|
92
|
+
key=${FACTORY_SSH_KEY:-$HOME/.ssh/agent-factory-azure}
|
|
93
|
+
[[ -f $key ]] || ssh-keygen -t ed25519 -f "$key" -N "" -C agent-factory-azure
|
|
94
|
+
operator=$(az ad signed-in-user show --query id --output tsv)
|
|
95
|
+
deployment=$(az deployment group create --name agent-factory-setup --resource-group "$RESOURCE_GROUP" --template-file "$(dirname "$0")/../infra/main.bicep" --parameters adminSshKey="$(< "$key.pub")" operatorObjectId="$operator" --output json)
|
|
96
|
+
vault=$(jq -r '.properties.outputs.keyVaultName.value' <<<"$deployment")
|
|
97
|
+
namespace=$(jq -r '.properties.outputs.serviceBusNamespace.value' <<<"$deployment")
|
|
98
|
+
VAULT=$vault
|
|
99
|
+
NAMESPACE=$namespace
|
|
100
|
+
mkdir -p "$(dirname "$CONFIG_FILE")"
|
|
101
|
+
{
|
|
102
|
+
printf 'FACTORY_RESOURCE_GROUP=%q\n' "$RESOURCE_GROUP"
|
|
103
|
+
printf 'FACTORY_VM=%q\n' agent-factory-vm
|
|
104
|
+
printf 'FACTORY_SERVICE_BUS=%q\n' "$namespace"
|
|
105
|
+
printf 'FACTORY_KEY_VAULT=%q\n' "$vault"
|
|
106
|
+
} > "$CONFIG_FILE"
|
|
107
|
+
chmod 0600 "$CONFIG_FILE"
|
|
108
|
+
ip=$(curl -fsS https://api.ipify.org)
|
|
109
|
+
cleanup_setup_vault() { az keyvault network-rule remove --name "$vault" --ip-address "$ip/32" --output none >/dev/null 2>&1 || true; rm -f "$choices"; }
|
|
110
|
+
trap cleanup_setup_vault EXIT
|
|
111
|
+
az keyvault network-rule add --name "$vault" --ip-address "$ip/32" --output none
|
|
112
|
+
sleep 10
|
|
113
|
+
github_token=$(gh auth token)
|
|
114
|
+
az keyvault secret set --vault-name "$vault" --name github-token --value "$github_token" --output none
|
|
115
|
+
unset github_token
|
|
116
|
+
if [[ -n $enterprise_org ]]; then
|
|
117
|
+
gh api "orgs/$enterprise_org" --silent
|
|
118
|
+
az keyvault secret set --vault-name "$vault" --name github-enterprise-org --value "$enterprise_org" --output none
|
|
119
|
+
fi
|
|
120
|
+
if [[ $provider == azure || $provider == both ]]; then
|
|
121
|
+
read -r -p 'Azure OpenAI-compatible base URL: ' azure_base
|
|
122
|
+
read -r -s -p 'Azure API key: ' azure_key; printf '\n'
|
|
123
|
+
read -r -p 'Separate GPT-5.4 base URL (Enter to reuse): ' azure_small_base
|
|
124
|
+
[[ -n $azure_small_base ]] || azure_small_base=$azure_base
|
|
125
|
+
read -r -s -p 'Separate GPT-5.4 key (Enter to reuse): ' azure_small_key; printf '\n'
|
|
126
|
+
[[ -n $azure_small_key ]] || azure_small_key=$azure_key
|
|
127
|
+
az keyvault secret set --vault-name "$vault" --name azure-primary-base-url --value "$azure_base" --output none
|
|
128
|
+
az keyvault secret set --vault-name "$vault" --name azure-primary-api-key --value "$azure_key" --output none
|
|
129
|
+
az keyvault secret set --vault-name "$vault" --name azure-small-base-url --value "$azure_small_base" --output none
|
|
130
|
+
az keyvault secret set --vault-name "$vault" --name azure-small-api-key --value "$azure_small_key" --output none
|
|
131
|
+
unset azure_key azure_small_key
|
|
132
|
+
fi
|
|
133
|
+
if [[ $provider == bedrock || $provider == both ]]; then
|
|
134
|
+
read -r -p 'AWS access key ID: ' aws_access_key
|
|
135
|
+
read -r -s -p 'AWS secret access key: ' aws_secret_key; printf '\n'
|
|
136
|
+
read -r -s -p 'AWS session token (optional): ' aws_session_token; printf '\n'
|
|
137
|
+
az keyvault secret set --vault-name "$vault" --name aws-access-key-id --value "$aws_access_key" --output none
|
|
138
|
+
az keyvault secret set --vault-name "$vault" --name aws-secret-access-key --value "$aws_secret_key" --output none
|
|
139
|
+
[[ -n $aws_session_token ]] && az keyvault secret set --vault-name "$vault" --name aws-session-token --value "$aws_session_token" --output none
|
|
140
|
+
unset aws_access_key aws_secret_key aws_session_token
|
|
141
|
+
fi
|
|
142
|
+
if [[ $telegram == true ]]; then
|
|
143
|
+
read -r -s -p 'Telegram bot token from @BotFather: ' telegram_token; printf '\n'
|
|
144
|
+
read -r -p 'Allowed Telegram chat IDs (comma-separated): ' telegram_chat_ids
|
|
145
|
+
[[ $telegram_chat_ids =~ ^-?[0-9]+(,-?[0-9]+)*$ ]] || { printf 'Invalid Telegram chat ID allowlist\n' >&2; exit 2; }
|
|
146
|
+
az keyvault secret set --vault-name "$vault" --name telegram-bot-token --value "$telegram_token" --output none
|
|
147
|
+
az keyvault secret set --vault-name "$vault" --name telegram-allowed-chat-ids --value "$telegram_chat_ids" --output none
|
|
148
|
+
unset telegram_token
|
|
149
|
+
fi
|
|
150
|
+
printf 'Azure foundation and credential vault ready. Config: %s\n' "$CONFIG_FILE"
|
|
151
|
+
if [[ $deploy_now == true ]]; then
|
|
152
|
+
source_ref=$(gh api repos/itsvedantkumar/factory-ai/commits/main --jq .sha)
|
|
153
|
+
parameters=(KEY_VAULT_NAME "$vault" SERVICE_BUS_NAMESPACE "$namespace" SERVICE_BUS_QUEUE code-tasks SOURCE_REPOSITORY itsvedantkumar/factory-ai SOURCE_REF "$source_ref")
|
|
154
|
+
if [[ $provider == bedrock ]]; then
|
|
155
|
+
parameters+=(AWS_REGION "$aws_region")
|
|
156
|
+
for role in SCOUT PLANNER BUILDER TESTER DEBUGGER REVIEWER SECURITY RELEASE; do parameters+=("FACTORY_MODEL_$role" "bedrock/$bedrock_builder"); done
|
|
157
|
+
elif [[ $provider == both ]]; then
|
|
158
|
+
parameters+=(AWS_REGION "$aws_region" FACTORY_MODEL_BUILDER "bedrock/$bedrock_builder")
|
|
159
|
+
fi
|
|
160
|
+
az vm run-command invoke --resource-group "$RESOURCE_GROUP" --name agent-factory-vm --command-id RunShellScript \
|
|
161
|
+
--scripts @"$(dirname "$0")/../bootstrap/deploy-runtime.sh" --parameters "${parameters[@]}" --output none
|
|
162
|
+
printf 'Runtime deployed. Run: factory doctor && factory dashboard\n'
|
|
163
|
+
fi
|
|
164
|
+
;;
|
|
165
|
+
github)
|
|
166
|
+
action=${1:-status}
|
|
167
|
+
case "$action" in
|
|
168
|
+
status)
|
|
169
|
+
gh auth status
|
|
170
|
+
printf 'Organizations:\n'
|
|
171
|
+
gh api user/orgs --jq '.[].login'
|
|
172
|
+
;;
|
|
173
|
+
connect)
|
|
174
|
+
org=${2:?Enterprise organization required}
|
|
175
|
+
gh api "orgs/$org" --jq '{login,plan:.plan.name}'
|
|
176
|
+
token=$(gh auth token)
|
|
177
|
+
ip=$(curl -fsS https://api.ipify.org)
|
|
178
|
+
cleanup_github_vault() { az keyvault network-rule remove --name "$VAULT" --ip-address "$ip/32" --output none >/dev/null 2>&1 || true; }
|
|
179
|
+
trap cleanup_github_vault EXIT
|
|
180
|
+
az keyvault network-rule add --name "$VAULT" --ip-address "$ip/32" --output none
|
|
181
|
+
sleep 5
|
|
182
|
+
az keyvault secret set --vault-name "$VAULT" --name github-token --value "$token" --output none
|
|
183
|
+
az keyvault secret set --vault-name "$VAULT" --name github-enterprise-org --value "$org" --output none
|
|
184
|
+
unset token
|
|
185
|
+
printf 'Connected organization %s. Restart release/worker services after credential changes.\n' "$org"
|
|
186
|
+
;;
|
|
187
|
+
transfer)
|
|
188
|
+
repo=${2:?OWNER/REPO required}
|
|
189
|
+
org=${3:?Enterprise organization required}
|
|
190
|
+
gh api --method POST "repos/$repo/transfer" -f new_owner="$org" --silent
|
|
191
|
+
printf 'Transfer requested: %s -> %s. Update submit paths after GitHub completes it.\n' "$repo" "$org"
|
|
192
|
+
;;
|
|
193
|
+
*) printf 'Unknown github action: %s\n' "$action" >&2; exit 2 ;;
|
|
194
|
+
esac
|
|
195
|
+
;;
|
|
196
|
+
secret)
|
|
197
|
+
action=${1:-list}
|
|
198
|
+
name=${2:-}
|
|
199
|
+
ip=$(curl -fsS https://api.ipify.org)
|
|
200
|
+
cleanup_vault_rule() { az keyvault network-rule remove --name "$VAULT" --ip-address "$ip/32" --output none >/dev/null 2>&1 || true; }
|
|
201
|
+
trap cleanup_vault_rule EXIT
|
|
202
|
+
az keyvault network-rule add --name "$VAULT" --ip-address "$ip/32" --output none
|
|
203
|
+
sleep 5
|
|
204
|
+
case "$action" in
|
|
205
|
+
list) az keyvault secret list --vault-name "$VAULT" --query '[].{name:name,updated:attributes.updated}' --output table ;;
|
|
206
|
+
set)
|
|
207
|
+
[[ $name =~ ^[A-Za-z0-9-]+$ ]] || { printf 'Valid secret name required\n' >&2; exit 2; }
|
|
208
|
+
read -r -s -p "Value for $name: " value
|
|
209
|
+
printf '\n'
|
|
210
|
+
az keyvault secret set --vault-name "$VAULT" --name "$name" --value "$value" --output none
|
|
211
|
+
unset value
|
|
212
|
+
printf 'Stored %s in Azure Key Vault.\n' "$name"
|
|
213
|
+
;;
|
|
214
|
+
copy)
|
|
215
|
+
[[ $name =~ ^[A-Za-z0-9-]+$ ]] || { printf 'Valid secret name required\n' >&2; exit 2; }
|
|
216
|
+
az keyvault secret show --vault-name "$VAULT" --name "$name" --query value --output tsv | pbcopy
|
|
217
|
+
printf 'Copied %s to clipboard without printing it.\n' "$name"
|
|
218
|
+
;;
|
|
219
|
+
delete)
|
|
220
|
+
[[ $name =~ ^[A-Za-z0-9-]+$ ]] || { printf 'Valid secret name required\n' >&2; exit 2; }
|
|
221
|
+
az keyvault secret delete --vault-name "$VAULT" --name "$name" --output none
|
|
222
|
+
printf 'Soft-deleted %s.\n' "$name"
|
|
223
|
+
;;
|
|
224
|
+
*) printf 'Unknown secret action: %s\n' "$action" >&2; exit 2 ;;
|
|
225
|
+
esac
|
|
226
|
+
;;
|
|
227
|
+
init)
|
|
228
|
+
target=${1:?local project path required}
|
|
229
|
+
[[ -d $target ]] || { printf 'Project directory does not exist: %s\n' "$target" >&2; exit 2; }
|
|
230
|
+
context="$target/.agent-factory"
|
|
231
|
+
mkdir -p "$context"
|
|
232
|
+
for template in "$(dirname "$0")/../templates/project/"*.md; do
|
|
233
|
+
destination="$context/$(basename "$template")"
|
|
234
|
+
[[ -e $destination ]] || cp "$template" "$destination"
|
|
235
|
+
done
|
|
236
|
+
printf 'Initialized %s\nShared runtime memory remains on Azure; repo context lives in .agent-factory/.\n' "$context"
|
|
237
|
+
;;
|
|
238
|
+
submit)
|
|
239
|
+
repo=${1:?repository owner/name required}
|
|
240
|
+
shift
|
|
241
|
+
objective=${*:?objective required}
|
|
242
|
+
[[ $repo =~ ^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$ ]] || { printf 'Invalid repository\n' >&2; exit 2; }
|
|
243
|
+
repo64=$(printf '%s' "$repo" | base64)
|
|
244
|
+
objective64=$(printf '%s' "$objective" | base64)
|
|
245
|
+
remote "repo=\$(printf '%s' '$repo64' | base64 -d); objective=\$(printf '%s' '$objective64' | base64 -d); sudo -u factory env \$(xargs < /etc/agent-factory-control.env) node /opt/agent-factory/app/src/ceo.js --repo \"https://github.com/\$repo.git\" --base main \"\$objective\""
|
|
246
|
+
;;
|
|
247
|
+
dashboard|status)
|
|
248
|
+
remote 'sudo -u factory env $(xargs < /etc/agent-factory-control.env) node /opt/agent-factory/app/src/dashboard.js'
|
|
249
|
+
;;
|
|
250
|
+
queue)
|
|
251
|
+
az servicebus queue list --resource-group "$RESOURCE_GROUP" --namespace-name "$NAMESPACE" \
|
|
252
|
+
--query '[].{queue:name,active:countDetails.activeMessageCount,deadLetter:countDetails.deadLetterMessageCount}' --output table
|
|
253
|
+
;;
|
|
254
|
+
report)
|
|
255
|
+
remote 'node -e '\''const fs=require("fs"),p="/opt/agent-factory/state/reports";const f=fs.readdirSync(p).filter(x=>x.endsWith(".md")).sort().at(-1);process.stdout.write(fs.readFileSync(`${p}/${f}`,"utf8"))'\'''
|
|
256
|
+
;;
|
|
257
|
+
logs)
|
|
258
|
+
remote 'journalctl -u agent-factory-control -u agent-factory-worker -u agent-factory-release --since "1 hour ago" --no-pager -n 250'
|
|
259
|
+
;;
|
|
260
|
+
doctor)
|
|
261
|
+
remote 'systemctl is-active agent-factory-control agent-factory-worker agent-factory-release; systemctl is-enabled agent-factory-reporter.timer; docker ps --format "{{.Names}} {{.Status}}"; findmnt /opt/agent-factory/state; df -h /opt/agent-factory/state; free -h'
|
|
262
|
+
;;
|
|
263
|
+
pause)
|
|
264
|
+
remote 'systemctl stop agent-factory-worker agent-factory-release'
|
|
265
|
+
;;
|
|
266
|
+
resume)
|
|
267
|
+
remote 'systemctl start agent-factory-worker agent-factory-release; systemctl is-active agent-factory-worker agent-factory-release'
|
|
268
|
+
;;
|
|
269
|
+
help|-h|--help) usage ;;
|
|
270
|
+
*) usage >&2; exit 2 ;;
|
|
271
|
+
esac
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
[Unit]
|
|
2
|
+
Description=Factory AI capability-free control plane
|
|
3
|
+
After=network-online.target
|
|
4
|
+
Wants=network-online.target
|
|
5
|
+
StartLimitIntervalSec=300
|
|
6
|
+
StartLimitBurst=10
|
|
7
|
+
|
|
8
|
+
[Service]
|
|
9
|
+
Type=simple
|
|
10
|
+
User=factory
|
|
11
|
+
Group=factory
|
|
12
|
+
WorkingDirectory=/opt/agent-factory/app
|
|
13
|
+
EnvironmentFile=/etc/agent-factory-control.env
|
|
14
|
+
ExecStart=/usr/bin/node /opt/agent-factory/app/src/control-service.js
|
|
15
|
+
Restart=on-failure
|
|
16
|
+
RestartSec=10
|
|
17
|
+
TimeoutStopSec=45
|
|
18
|
+
NoNewPrivileges=true
|
|
19
|
+
PrivateTmp=true
|
|
20
|
+
ProtectSystem=strict
|
|
21
|
+
ProtectHome=true
|
|
22
|
+
ProtectKernelTunables=true
|
|
23
|
+
ProtectKernelModules=true
|
|
24
|
+
ProtectKernelLogs=true
|
|
25
|
+
ProtectControlGroups=true
|
|
26
|
+
LockPersonality=true
|
|
27
|
+
RestrictSUIDSGID=true
|
|
28
|
+
ReadWritePaths=/opt/agent-factory/state
|
|
29
|
+
UMask=0027
|
|
30
|
+
|
|
31
|
+
[Install]
|
|
32
|
+
WantedBy=multi-user.target
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
[Unit]
|
|
2
|
+
Description=Factory AI trusted release bot
|
|
3
|
+
After=network-online.target
|
|
4
|
+
Wants=network-online.target
|
|
5
|
+
|
|
6
|
+
[Service]
|
|
7
|
+
Type=simple
|
|
8
|
+
User=factory
|
|
9
|
+
Group=factory
|
|
10
|
+
WorkingDirectory=/opt/agent-factory/app
|
|
11
|
+
EnvironmentFile=/etc/agent-factory.env
|
|
12
|
+
Environment=HOME=/opt/agent-factory/state/home
|
|
13
|
+
ExecStart=/usr/bin/node /opt/agent-factory/app/src/release-service.js
|
|
14
|
+
Restart=on-failure
|
|
15
|
+
RestartSec=10
|
|
16
|
+
TimeoutStopSec=45
|
|
17
|
+
NoNewPrivileges=true
|
|
18
|
+
PrivateTmp=true
|
|
19
|
+
ProtectSystem=strict
|
|
20
|
+
ProtectHome=true
|
|
21
|
+
ReadWritePaths=/opt/agent-factory/state /opt/agent-factory/logs
|
|
22
|
+
UMask=0027
|
|
23
|
+
|
|
24
|
+
[Install]
|
|
25
|
+
WantedBy=multi-user.target
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
[Unit]
|
|
2
|
+
Description=Factory AI hourly progress report
|
|
3
|
+
After=agent-factory-worker.service
|
|
4
|
+
|
|
5
|
+
[Service]
|
|
6
|
+
Type=oneshot
|
|
7
|
+
User=factory
|
|
8
|
+
Group=factory
|
|
9
|
+
WorkingDirectory=/opt/agent-factory/app
|
|
10
|
+
EnvironmentFile=/etc/agent-factory.env
|
|
11
|
+
ExecStart=/usr/bin/node /opt/agent-factory/app/src/reporter.js
|
|
12
|
+
NoNewPrivileges=true
|
|
13
|
+
PrivateTmp=true
|
|
14
|
+
ProtectSystem=strict
|
|
15
|
+
ProtectHome=true
|
|
16
|
+
ReadWritePaths=/opt/agent-factory/state
|
|
17
|
+
UMask=0027
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
[Unit]
|
|
2
|
+
Description=Factory AI Telegram remote objective intake
|
|
3
|
+
After=network-online.target agent-factory-control.service
|
|
4
|
+
Wants=network-online.target
|
|
5
|
+
|
|
6
|
+
[Service]
|
|
7
|
+
Type=simple
|
|
8
|
+
User=factory
|
|
9
|
+
Group=factory
|
|
10
|
+
WorkingDirectory=/opt/agent-factory/app
|
|
11
|
+
EnvironmentFile=/etc/agent-factory.env
|
|
12
|
+
Environment=HOME=/opt/agent-factory/state/home
|
|
13
|
+
ExecStart=/usr/bin/node /opt/agent-factory/app/src/telegram-service.js
|
|
14
|
+
Restart=on-failure
|
|
15
|
+
RestartSec=10
|
|
16
|
+
TimeoutStopSec=40
|
|
17
|
+
NoNewPrivileges=true
|
|
18
|
+
PrivateTmp=true
|
|
19
|
+
ProtectSystem=strict
|
|
20
|
+
ProtectHome=true
|
|
21
|
+
ProtectKernelTunables=true
|
|
22
|
+
ProtectKernelModules=true
|
|
23
|
+
ProtectKernelLogs=true
|
|
24
|
+
ProtectControlGroups=true
|
|
25
|
+
LockPersonality=true
|
|
26
|
+
RestrictSUIDSGID=true
|
|
27
|
+
ReadWritePaths=/opt/agent-factory/state/telegram
|
|
28
|
+
UMask=0027
|
|
29
|
+
|
|
30
|
+
[Install]
|
|
31
|
+
WantedBy=multi-user.target
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
[Unit]
|
|
2
|
+
Description=Factory AI durable worker
|
|
3
|
+
After=network-online.target docker.service
|
|
4
|
+
Wants=network-online.target
|
|
5
|
+
Requires=docker.service
|
|
6
|
+
StartLimitIntervalSec=300
|
|
7
|
+
StartLimitBurst=10
|
|
8
|
+
|
|
9
|
+
[Service]
|
|
10
|
+
Type=simple
|
|
11
|
+
User=factory
|
|
12
|
+
Group=factory
|
|
13
|
+
WorkingDirectory=/opt/agent-factory/app
|
|
14
|
+
EnvironmentFile=/etc/agent-factory.env
|
|
15
|
+
Environment=HOME=/opt/agent-factory/state/home
|
|
16
|
+
Environment=XDG_CACHE_HOME=/opt/agent-factory/state/home/.cache
|
|
17
|
+
Environment=XDG_CONFIG_HOME=/opt/agent-factory/state/home/.config
|
|
18
|
+
ExecStart=/usr/bin/node /opt/agent-factory/app/src/worker.js
|
|
19
|
+
Restart=on-failure
|
|
20
|
+
RestartSec=10
|
|
21
|
+
TimeoutStopSec=45
|
|
22
|
+
KillSignal=SIGTERM
|
|
23
|
+
NoNewPrivileges=true
|
|
24
|
+
PrivateTmp=true
|
|
25
|
+
ProtectSystem=strict
|
|
26
|
+
ProtectHome=true
|
|
27
|
+
ProtectKernelTunables=true
|
|
28
|
+
ProtectKernelModules=true
|
|
29
|
+
ProtectKernelLogs=true
|
|
30
|
+
ProtectControlGroups=true
|
|
31
|
+
LockPersonality=true
|
|
32
|
+
RestrictSUIDSGID=true
|
|
33
|
+
SystemCallArchitectures=native
|
|
34
|
+
SupplementaryGroups=docker
|
|
35
|
+
ReadWritePaths=/opt/agent-factory/state /opt/agent-factory/workspaces /opt/agent-factory/logs
|
|
36
|
+
UMask=0027
|
|
37
|
+
|
|
38
|
+
[Install]
|
|
39
|
+
WantedBy=multi-user.target
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
#cloud-config
|
|
2
|
+
package_update: true
|
|
3
|
+
package_upgrade: true
|
|
4
|
+
packages:
|
|
5
|
+
- ca-certificates
|
|
6
|
+
- curl
|
|
7
|
+
- docker-compose-v2
|
|
8
|
+
- docker.io
|
|
9
|
+
- fail2ban
|
|
10
|
+
- git
|
|
11
|
+
- jq
|
|
12
|
+
- postgresql-client
|
|
13
|
+
- ripgrep
|
|
14
|
+
- sqlite3
|
|
15
|
+
- unattended-upgrades
|
|
16
|
+
|
|
17
|
+
write_files:
|
|
18
|
+
- path: /etc/docker/daemon.json
|
|
19
|
+
permissions: '0644'
|
|
20
|
+
content: |
|
|
21
|
+
{
|
|
22
|
+
"log-driver": "json-file",
|
|
23
|
+
"log-opts": {
|
|
24
|
+
"max-size": "20m",
|
|
25
|
+
"max-file": "5"
|
|
26
|
+
},
|
|
27
|
+
"live-restore": true
|
|
28
|
+
}
|
|
29
|
+
- path: /etc/sysctl.d/99-agent-factory.conf
|
|
30
|
+
permissions: '0644'
|
|
31
|
+
content: |
|
|
32
|
+
fs.inotify.max_user_instances=1024
|
|
33
|
+
fs.inotify.max_user_watches=1048576
|
|
34
|
+
fs.file-max=2097152
|
|
35
|
+
- path: /etc/systemd/system/agent-factory-health.service
|
|
36
|
+
permissions: '0644'
|
|
37
|
+
content: |
|
|
38
|
+
[Unit]
|
|
39
|
+
Description=Agent factory host health check
|
|
40
|
+
After=network-online.target docker.service
|
|
41
|
+
Wants=network-online.target
|
|
42
|
+
|
|
43
|
+
[Service]
|
|
44
|
+
Type=oneshot
|
|
45
|
+
ExecStart=/usr/local/bin/agent-factory-health
|
|
46
|
+
- path: /etc/systemd/system/agent-factory-health.timer
|
|
47
|
+
permissions: '0644'
|
|
48
|
+
content: |
|
|
49
|
+
[Unit]
|
|
50
|
+
Description=Run agent factory host health check
|
|
51
|
+
|
|
52
|
+
[Timer]
|
|
53
|
+
OnBootSec=5min
|
|
54
|
+
OnUnitActiveSec=5min
|
|
55
|
+
Persistent=true
|
|
56
|
+
|
|
57
|
+
[Install]
|
|
58
|
+
WantedBy=timers.target
|
|
59
|
+
- path: /usr/local/bin/agent-factory-health
|
|
60
|
+
permissions: '0755'
|
|
61
|
+
content: |
|
|
62
|
+
#!/usr/bin/env bash
|
|
63
|
+
set -euo pipefail
|
|
64
|
+
test "$(systemctl is-active docker)" = active
|
|
65
|
+
test "$(df --output=pcent / | tail -1 | tr -dc '0-9')" -lt 90
|
|
66
|
+
test "$(awk '/MemAvailable/ {print $2}' /proc/meminfo)" -gt 1048576
|
|
67
|
+
|
|
68
|
+
runcmd:
|
|
69
|
+
- [mkdir, -p, /opt/agent-factory/workspaces, /opt/agent-factory/state, /opt/agent-factory/logs]
|
|
70
|
+
- [chmod, '0750', /opt/agent-factory]
|
|
71
|
+
- [systemctl, enable, --now, docker]
|
|
72
|
+
- [systemctl, enable, --now, fail2ban]
|
|
73
|
+
- [systemctl, enable, --now, unattended-upgrades]
|
|
74
|
+
- [systemctl, daemon-reload]
|
|
75
|
+
- [systemctl, enable, --now, agent-factory-health.timer]
|
|
76
|
+
- [sysctl, --system]
|
|
77
|
+
|
|
78
|
+
final_message: Agent factory base host ready after $UPTIME seconds
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
set +x
|
|
4
|
+
|
|
5
|
+
if [[ ${EUID} -ne 0 ]]; then
|
|
6
|
+
echo "deploy-runtime.sh must run as root" >&2
|
|
7
|
+
exit 1
|
|
8
|
+
fi
|
|
9
|
+
|
|
10
|
+
while (($# > 0)); do
|
|
11
|
+
if [[ $1 == *=* ]]; then
|
|
12
|
+
name=${1%%=*}
|
|
13
|
+
value=${1#*=}
|
|
14
|
+
shift
|
|
15
|
+
else
|
|
16
|
+
name=$1
|
|
17
|
+
shift
|
|
18
|
+
(($# > 0)) || { echo "Missing value for $name" >&2; exit 1; }
|
|
19
|
+
value=$1
|
|
20
|
+
shift
|
|
21
|
+
fi
|
|
22
|
+
case "$name" in
|
|
23
|
+
KEY_VAULT_NAME|SERVICE_BUS_NAMESPACE|SERVICE_BUS_QUEUE|SOURCE_REPOSITORY|SOURCE_REF|AWS_REGION|FACTORY_MODEL_SCOUT|FACTORY_MODEL_PLANNER|FACTORY_MODEL_BUILDER|FACTORY_MODEL_TESTER|FACTORY_MODEL_DEBUGGER|FACTORY_MODEL_REVIEWER|FACTORY_MODEL_SECURITY|FACTORY_MODEL_RELEASE) printf -v "$name" '%s' "$value" ;;
|
|
24
|
+
*) echo "Unknown deployment parameter: $name" >&2; exit 1 ;;
|
|
25
|
+
esac
|
|
26
|
+
done
|
|
27
|
+
|
|
28
|
+
: "${KEY_VAULT_NAME:?KEY_VAULT_NAME is required}"
|
|
29
|
+
: "${SERVICE_BUS_NAMESPACE:?SERVICE_BUS_NAMESPACE is required}"
|
|
30
|
+
: "${SOURCE_REPOSITORY:?SOURCE_REPOSITORY as OWNER/REPOSITORY is required}"
|
|
31
|
+
: "${SOURCE_REF:?SOURCE_REF must be a full commit SHA}"
|
|
32
|
+
SERVICE_BUS_QUEUE=${SERVICE_BUS_QUEUE:-code-tasks}
|
|
33
|
+
[[ $SOURCE_REPOSITORY =~ ^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$ ]] || { echo "Invalid SOURCE_REPOSITORY" >&2; exit 1; }
|
|
34
|
+
[[ $SOURCE_REF =~ ^[0-9a-f]{40}$ ]] || { echo "SOURCE_REF must be a full lowercase commit SHA" >&2; exit 1; }
|
|
35
|
+
|
|
36
|
+
export DEBIAN_FRONTEND=noninteractive
|
|
37
|
+
apt-get update
|
|
38
|
+
apt-get install -y ca-certificates curl git gpg rsync
|
|
39
|
+
install -d -m 0755 /etc/apt/keyrings
|
|
40
|
+
curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg -o /etc/apt/keyrings/githubcli-archive-keyring.gpg
|
|
41
|
+
chmod go+r /etc/apt/keyrings/githubcli-archive-keyring.gpg
|
|
42
|
+
printf '%s\n' "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" > /etc/apt/sources.list.d/github-cli.list
|
|
43
|
+
curl -fsSL https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor --yes -o /etc/apt/keyrings/microsoft.gpg
|
|
44
|
+
printf '%s\n' "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/microsoft.gpg] https://packages.microsoft.com/repos/azure-cli/ $(. /etc/os-release && printf '%s' "$VERSION_CODENAME") main" > /etc/apt/sources.list.d/azure-cli.list
|
|
45
|
+
apt-get update
|
|
46
|
+
apt-get install -y azure-cli gh
|
|
47
|
+
|
|
48
|
+
az login --identity --allow-no-subscriptions --output none
|
|
49
|
+
github_token=$(az keyvault secret show --vault-name "$KEY_VAULT_NAME" --name "${GITHUB_TOKEN_SECRET:-github-token}" --query value --output tsv)
|
|
50
|
+
[[ -n $github_token && $github_token != *$'\n'* && $github_token != *$'\r'* ]] || { echo "GitHub secret is empty or invalid" >&2; exit 1; }
|
|
51
|
+
|
|
52
|
+
temporary=$(mktemp -d /opt/agent-factory-source.XXXXXX)
|
|
53
|
+
trap 'rm -rf "$temporary"' EXIT
|
|
54
|
+
GH_TOKEN="$github_token" gh repo clone "$SOURCE_REPOSITORY" "$temporary/source" -- --no-checkout
|
|
55
|
+
unset github_token
|
|
56
|
+
git -C "$temporary/source" checkout --detach "$SOURCE_REF"
|
|
57
|
+
test "$(git -C "$temporary/source" rev-parse HEAD)" = "$SOURCE_REF"
|
|
58
|
+
install -d -m 0755 /opt/agent-factory/app
|
|
59
|
+
rsync -a --delete --exclude .git "$temporary/source/" /opt/agent-factory/app/
|
|
60
|
+
|
|
61
|
+
KEY_VAULT_NAME="$KEY_VAULT_NAME" \
|
|
62
|
+
SERVICE_BUS_NAMESPACE="$SERVICE_BUS_NAMESPACE" \
|
|
63
|
+
SERVICE_BUS_QUEUE="$SERVICE_BUS_QUEUE" \
|
|
64
|
+
FACTORY_WORKER_IMAGE="agent-factory-worker:$SOURCE_REF" \
|
|
65
|
+
AWS_REGION="${AWS_REGION:-}" \
|
|
66
|
+
FACTORY_MODEL_SCOUT="${FACTORY_MODEL_SCOUT:-}" \
|
|
67
|
+
FACTORY_MODEL_PLANNER="${FACTORY_MODEL_PLANNER:-}" \
|
|
68
|
+
FACTORY_MODEL_BUILDER="${FACTORY_MODEL_BUILDER:-}" \
|
|
69
|
+
FACTORY_MODEL_TESTER="${FACTORY_MODEL_TESTER:-}" \
|
|
70
|
+
FACTORY_MODEL_DEBUGGER="${FACTORY_MODEL_DEBUGGER:-}" \
|
|
71
|
+
FACTORY_MODEL_REVIEWER="${FACTORY_MODEL_REVIEWER:-}" \
|
|
72
|
+
FACTORY_MODEL_SECURITY="${FACTORY_MODEL_SECURITY:-}" \
|
|
73
|
+
FACTORY_MODEL_RELEASE="${FACTORY_MODEL_RELEASE:-}" \
|
|
74
|
+
bash /opt/agent-factory/app/bootstrap/setup.sh
|