@mestreyoda/fabrica 0.2.15 → 0.2.16
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/README.md +71 -18
- package/dist/index.js +144 -52
- package/package.json +2 -4
- package/ARCHITECTURE.md +0 -93
- package/CHANGELOG.md +0 -42
package/README.md
CHANGED
|
@@ -96,7 +96,17 @@ operational/workspace state, then tells you what is still missing.
|
|
|
96
96
|
|
|
97
97
|
## Quick start
|
|
98
98
|
|
|
99
|
-
|
|
99
|
+
This is the minimum recommended path to get Fabrica working end-to-end with the official product flow.
|
|
100
|
+
|
|
101
|
+
**1. Authenticate GitHub CLI**:
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
gh auth status || gh auth login
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Fabrica uses authenticated `gh` CLI for GitHub operations in the default setup.
|
|
108
|
+
|
|
109
|
+
**2. Install Fabrica**:
|
|
100
110
|
|
|
101
111
|
```bash
|
|
102
112
|
openclaw plugins install @mestreyoda/fabrica
|
|
@@ -104,37 +114,69 @@ openclaw plugins install @mestreyoda/fabrica
|
|
|
104
114
|
|
|
105
115
|
The plugin should load immediately after install, without manual remediation.
|
|
106
116
|
|
|
107
|
-
**
|
|
117
|
+
**3. Confirm loadability**:
|
|
108
118
|
|
|
109
119
|
```bash
|
|
110
120
|
openclaw plugins inspect fabrica
|
|
111
121
|
```
|
|
112
122
|
|
|
113
|
-
**
|
|
123
|
+
**4. Configure Fabrica for a workspace**:
|
|
114
124
|
|
|
115
125
|
```bash
|
|
116
126
|
openclaw fabrica doctor workspace --workspace /path/to/workspace
|
|
117
127
|
openclaw fabrica setup --workspace /path/to/workspace --new-agent fabrica
|
|
118
128
|
```
|
|
119
129
|
|
|
120
|
-
Use `openclaw fabrica setup --agent <id>` if you already have an agent.
|
|
121
|
-
|
|
122
|
-
|
|
130
|
+
Use `openclaw fabrica setup --agent <id>` if you already have an agent.
|
|
131
|
+
|
|
132
|
+
When the official Telegram DM bootstrap flow is configured (`bootstrapDmEnabled=true`
|
|
133
|
+
plus `projectsForumChatId`), `openclaw fabrica setup` also prepares the dedicated
|
|
134
|
+
internal `genesis` agent automatically.
|
|
135
|
+
|
|
136
|
+
**5. Configure Telegram for the official Fabrica flow**:
|
|
137
|
+
|
|
138
|
+
The official flow is:
|
|
139
|
+
- Telegram DM with the bot for new-project intake
|
|
140
|
+
- one Telegram forum group for project topics/timelines
|
|
141
|
+
|
|
142
|
+
At minimum, when DM bootstrap is enabled, set:
|
|
143
|
+
- `plugins.entries.fabrica.config.telegram.bootstrapDmEnabled=true`
|
|
144
|
+
- `plugins.entries.fabrica.config.telegram.projectsForumChatId=<YOUR_PROJECTS_FORUM_CHAT_ID>`
|
|
145
|
+
|
|
146
|
+
If `projectsForumChatId` is missing while DM bootstrap is enabled, Fabrica can accept the DM but will fail when it needs to create the project topic.
|
|
147
|
+
|
|
148
|
+
**6. Validate operational readiness**:
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
openclaw plugins inspect fabrica
|
|
152
|
+
openclaw fabrica doctor workspace --workspace /path/to/workspace
|
|
153
|
+
```
|
|
123
154
|
|
|
124
155
|
**Environment provisioning note**:
|
|
125
156
|
|
|
126
|
-
Developer and tester pickup
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
157
|
+
Developer and tester pickup pass through a stack environment gate. Fabrica
|
|
158
|
+
prepares the project environment before dispatching real work, instead of
|
|
159
|
+
finding missing dependencies inside a live worker run.
|
|
160
|
+
|
|
161
|
+
For Python projects, this includes just-in-time `uv` installation when needed,
|
|
162
|
+
a shared toolchain, and a project-local `.venv`.
|
|
130
163
|
|
|
131
|
-
|
|
164
|
+
For existing Node projects, Fabrica expects a reproducible lockfile
|
|
165
|
+
(`package-lock.json`, `pnpm-lock.yaml`, `yarn.lock`, or `bun.lock`) before
|
|
166
|
+
real developer/tester dispatch. Greenfield scaffold mode can materialize the
|
|
167
|
+
first deterministic lockfile, but regular runtime pickup fails closed without
|
|
168
|
+
one.
|
|
169
|
+
|
|
170
|
+
`dryRun: true` skips environment provisioning entirely and remains side-effect
|
|
171
|
+
free.
|
|
172
|
+
|
|
173
|
+
**7. Restart the gateway if needed**:
|
|
132
174
|
|
|
133
175
|
```bash
|
|
134
176
|
systemctl --user restart openclaw-gateway.service
|
|
135
177
|
```
|
|
136
178
|
|
|
137
|
-
**
|
|
179
|
+
**8. Trigger a new project programmatically**:
|
|
138
180
|
|
|
139
181
|
```bash
|
|
140
182
|
cd ~/fabrica # GitHub clone install only
|
|
@@ -146,13 +188,13 @@ npx tsx scripts/genesis-trigger.ts "A CLI tool that counts words in a file" \
|
|
|
146
188
|
|
|
147
189
|
Remove `--dry-run` to execute for real.
|
|
148
190
|
|
|
149
|
-
**
|
|
191
|
+
**9. Watch the pipeline run**:
|
|
150
192
|
|
|
151
193
|
```bash
|
|
152
194
|
tail -f ~/.openclaw/workspace/logs/genesis.log
|
|
153
195
|
```
|
|
154
196
|
|
|
155
|
-
**
|
|
197
|
+
**10. Check metrics**:
|
|
156
198
|
|
|
157
199
|
```bash
|
|
158
200
|
openclaw fabrica metrics
|
|
@@ -223,7 +265,11 @@ Use plugin config when you want explicit webhook behavior or provider auth profi
|
|
|
223
265
|
|
|
224
266
|
### With Telegram
|
|
225
267
|
|
|
226
|
-
Telegram
|
|
268
|
+
Telegram is the primary human-facing entrypoint for Fabrica:
|
|
269
|
+
- DM with the bot for new-project intake and short clarifications
|
|
270
|
+
- one Telegram forum group where Fabrica creates one topic per project
|
|
271
|
+
|
|
272
|
+
Recommended minimum Telegram configuration:
|
|
227
273
|
|
|
228
274
|
```json
|
|
229
275
|
{
|
|
@@ -243,8 +289,7 @@ Telegram enables DM-based project bootstrap, per-project forum topics, and a sep
|
|
|
243
289
|
"telegram": {
|
|
244
290
|
"bootstrapDmEnabled": true,
|
|
245
291
|
"projectsForumChatId": "<YOUR_PROJECTS_FORUM_CHAT_ID>",
|
|
246
|
-
"projectsForumAccountId": "<OPTIONAL_TELEGRAM_ACCOUNT_ID>"
|
|
247
|
-
"opsChatId": "<YOUR_OPS_CHAT_ID>"
|
|
292
|
+
"projectsForumAccountId": "<OPTIONAL_TELEGRAM_ACCOUNT_ID>"
|
|
248
293
|
}
|
|
249
294
|
}
|
|
250
295
|
}
|
|
@@ -253,7 +298,15 @@ Telegram enables DM-based project bootstrap, per-project forum topics, and a sep
|
|
|
253
298
|
}
|
|
254
299
|
```
|
|
255
300
|
|
|
256
|
-
|
|
301
|
+
`projectsForumChatId` is the key Fabrica-specific Telegram setting for the official DM → topic flow.
|
|
302
|
+
|
|
303
|
+
When both `bootstrapDmEnabled=true` and `projectsForumChatId` are present,
|
|
304
|
+
`openclaw fabrica setup` automatically prepares the internal `genesis` agent used
|
|
305
|
+
for the DM intake path.
|
|
306
|
+
|
|
307
|
+
`opsChatId` still exists in plugin config for deployments that want a separate ops-only route, but it is not required for the core product flow.
|
|
308
|
+
|
|
309
|
+
With Telegram enabled, send a project idea to the bot in a DM. Fabrica will ask clarifying questions, provision the GitHub repo, create a dedicated forum topic for the project, and continue the project lifecycle in that topic.
|
|
257
310
|
|
|
258
311
|
Project topics are event-driven timelines. Fabrica emits explicit messages for
|
|
259
312
|
worker start, worker completion, review queueing, reviewer reject/approve, and
|
package/dist/index.js
CHANGED
|
@@ -111347,8 +111347,8 @@ import fsSync from "node:fs";
|
|
|
111347
111347
|
import path5 from "node:path";
|
|
111348
111348
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
111349
111349
|
function getCurrentVersion() {
|
|
111350
|
-
if ("0.2.
|
|
111351
|
-
return "0.2.
|
|
111350
|
+
if ("0.2.16") {
|
|
111351
|
+
return "0.2.16";
|
|
111352
111352
|
}
|
|
111353
111353
|
try {
|
|
111354
111354
|
const pkgPath = path5.join(THIS_DIR, "..", "..", "package.json");
|
|
@@ -127660,6 +127660,7 @@ async function registerProject(params) {
|
|
|
127660
127660
|
}
|
|
127661
127661
|
await writeProjects(workspaceDir, data);
|
|
127662
127662
|
projectsPersisted = true;
|
|
127663
|
+
let finalResolvedConfig = resolvedConfig;
|
|
127663
127664
|
if (autonomousProject) {
|
|
127664
127665
|
workflowOverrideCreated = await ensureAutonomousWorkflowOverride(
|
|
127665
127666
|
workspaceDir,
|
|
@@ -127676,6 +127677,7 @@ async function registerProject(params) {
|
|
|
127676
127677
|
`Autonomous DM bootstrap resolved reviewPolicy="${persistedConfig.workflow.reviewPolicy}" for "${slug}", expected "${expectedReviewPolicy}"`
|
|
127677
127678
|
);
|
|
127678
127679
|
}
|
|
127680
|
+
finalResolvedConfig = persistedConfig;
|
|
127679
127681
|
}
|
|
127680
127682
|
promptsCreated = await scaffoldPromptFiles(workspaceDir, slug);
|
|
127681
127683
|
await log(workspaceDir, "project_register", {
|
|
@@ -127690,7 +127692,7 @@ async function registerProject(params) {
|
|
|
127690
127692
|
deployUrl: deployUrl || null,
|
|
127691
127693
|
isNewProject: !existing,
|
|
127692
127694
|
workflowOverrideCreated,
|
|
127693
|
-
reviewPolicy:
|
|
127695
|
+
reviewPolicy: finalResolvedConfig.workflow.reviewPolicy ?? "human"
|
|
127694
127696
|
});
|
|
127695
127697
|
const action = existing ? "Channel added to existing project" : `Project "${name}" created`;
|
|
127696
127698
|
const promptsNote = promptsCreated ? " Prompt files scaffolded." : "";
|
|
@@ -127710,8 +127712,8 @@ async function registerProject(params) {
|
|
|
127710
127712
|
workflowOverrideCreated,
|
|
127711
127713
|
isNewProject: !existing,
|
|
127712
127714
|
activeWorkflow: {
|
|
127713
|
-
reviewPolicy:
|
|
127714
|
-
testPhase: Object.values(
|
|
127715
|
+
reviewPolicy: finalResolvedConfig.workflow.reviewPolicy ?? "human",
|
|
127716
|
+
testPhase: Object.values(finalResolvedConfig.workflow.states).some(
|
|
127715
127717
|
(s2) => s2.role === "tester" && (s2.type === "queue" || s2.type === "active")
|
|
127716
127718
|
),
|
|
127717
127719
|
hint: "The user can change the review policy or enable the test phase \u2014 call workflow_guide for the full reference."
|
|
@@ -129995,9 +129997,11 @@ async function ensureGenesisAgent(runtime, runCommand, opts) {
|
|
|
129995
129997
|
throw new Error(`Failed to create genesis agent: ${err.message}`);
|
|
129996
129998
|
}
|
|
129997
129999
|
const updatedConfig = runtime.config.loadConfig();
|
|
129998
|
-
const bindings = (updatedConfig.bindings ?? []).filter(
|
|
129999
|
-
|
|
130000
|
-
|
|
130000
|
+
const bindings = (updatedConfig.bindings ?? []).filter((b) => {
|
|
130001
|
+
const isMainChannelWideTelegram = b.match?.channel === "telegram" && !b.match?.peer && b.agentId === "main";
|
|
130002
|
+
if (!isMainChannelWideTelegram) return true;
|
|
130003
|
+
return !opts?.forumGroupId;
|
|
130004
|
+
});
|
|
130001
130005
|
if (!bindings.some((b) => b.agentId === "genesis" && b.match?.channel === "telegram" && !b.match?.peer)) {
|
|
130002
130006
|
bindings.push({ agentId: "genesis", match: { channel: "telegram" } });
|
|
130003
130007
|
}
|
|
@@ -135830,12 +135834,13 @@ async function ensurePythonToolchain(runCommand, homeDir) {
|
|
|
135830
135834
|
}
|
|
135831
135835
|
await fs32.rm(toolchainPath, { recursive: true, force: true });
|
|
135832
135836
|
}
|
|
135837
|
+
const uvCmd = await ensureUv(runCommand);
|
|
135833
135838
|
await fs32.mkdir(path32.dirname(toolchainPath), { recursive: true });
|
|
135834
|
-
const venvResult = await runCommand(
|
|
135839
|
+
const venvResult = await runCommand(uvCmd, ["venv", toolchainPath], { timeout: 6e4 });
|
|
135835
135840
|
if (venvResult.exitCode !== 0) {
|
|
135836
135841
|
throw new Error(`Failed to create toolchain venv: ${venvResult.stderr}`);
|
|
135837
135842
|
}
|
|
135838
|
-
const installResult = await runCommand(
|
|
135843
|
+
const installResult = await runCommand(uvCmd, [
|
|
135839
135844
|
"pip",
|
|
135840
135845
|
"install",
|
|
135841
135846
|
"-p",
|
|
@@ -136103,7 +136108,7 @@ async function ensurePythonEnvironment(repoPath, stack, runCommand) {
|
|
|
136103
136108
|
fingerprint
|
|
136104
136109
|
};
|
|
136105
136110
|
}
|
|
136106
|
-
await ensureUv(runCommand);
|
|
136111
|
+
const uvCmd = await ensureUv(runCommand);
|
|
136107
136112
|
const uvLock = await pathExists2(path32.join(repoPath, "uv.lock"));
|
|
136108
136113
|
const pyproject = await pathExists2(path32.join(repoPath, "pyproject.toml"));
|
|
136109
136114
|
const requirements = await pathExists2(path32.join(repoPath, "requirements.txt"));
|
|
@@ -136122,10 +136127,25 @@ async function ensurePythonEnvironment(repoPath, stack, runCommand) {
|
|
|
136122
136127
|
reason: "missing_pyproject_or_requirements"
|
|
136123
136128
|
};
|
|
136124
136129
|
}
|
|
136130
|
+
if (uvLock && !pyproject) {
|
|
136131
|
+
return {
|
|
136132
|
+
ready: false,
|
|
136133
|
+
skipped: false,
|
|
136134
|
+
stack,
|
|
136135
|
+
family: "python",
|
|
136136
|
+
toolchain: "uv",
|
|
136137
|
+
packageManager: "uv",
|
|
136138
|
+
lockfile: "uv.lock",
|
|
136139
|
+
environmentPath: null,
|
|
136140
|
+
commandsRun,
|
|
136141
|
+
fingerprint,
|
|
136142
|
+
reason: "uv_lock_requires_pyproject"
|
|
136143
|
+
};
|
|
136144
|
+
}
|
|
136125
136145
|
await ensurePythonToolchain(runCommand);
|
|
136126
136146
|
if (uvLock) {
|
|
136127
136147
|
await runAndAssert(runCommand, repoPath, commandsRun, {
|
|
136128
|
-
cmd:
|
|
136148
|
+
cmd: uvCmd,
|
|
136129
136149
|
args: ["sync", "--locked"],
|
|
136130
136150
|
reason: "uv sync"
|
|
136131
136151
|
});
|
|
@@ -136144,13 +136164,13 @@ async function ensurePythonEnvironment(repoPath, stack, runCommand) {
|
|
|
136144
136164
|
};
|
|
136145
136165
|
}
|
|
136146
136166
|
await runAndAssert(runCommand, repoPath, commandsRun, {
|
|
136147
|
-
cmd:
|
|
136167
|
+
cmd: uvCmd,
|
|
136148
136168
|
args: ["venv", ".venv"],
|
|
136149
136169
|
reason: "create project-local virtualenv with uv"
|
|
136150
136170
|
});
|
|
136151
|
-
if (requirements) {
|
|
136171
|
+
if (requirements && !pyproject) {
|
|
136152
136172
|
await runAndAssert(runCommand, repoPath, commandsRun, {
|
|
136153
|
-
cmd:
|
|
136173
|
+
cmd: uvCmd,
|
|
136154
136174
|
args: ["pip", "install", "--python", ".venv/bin/python", "-r", "requirements.txt"],
|
|
136155
136175
|
reason: "install requirements.txt dependencies with uv"
|
|
136156
136176
|
});
|
|
@@ -136158,7 +136178,7 @@ async function ensurePythonEnvironment(repoPath, stack, runCommand) {
|
|
|
136158
136178
|
if (pyproject) {
|
|
136159
136179
|
const hasDevExtra = await hasPyprojectDevExtra(repoPath);
|
|
136160
136180
|
await runAndAssert(runCommand, repoPath, commandsRun, {
|
|
136161
|
-
cmd:
|
|
136181
|
+
cmd: uvCmd,
|
|
136162
136182
|
args: ["pip", "install", "--python", ".venv/bin/python", "-e", hasDevExtra ? ".[dev]" : "."],
|
|
136163
136183
|
reason: hasDevExtra ? "install editable project with dev extras via uv" : "install editable project via uv"
|
|
136164
136184
|
});
|
|
@@ -137436,6 +137456,7 @@ function getProjectEnvironmentState(project, stack) {
|
|
|
137436
137456
|
|
|
137437
137457
|
// lib/test-env/runtime.ts
|
|
137438
137458
|
var STALE_PROVISIONING_WINDOW_MS = 10 * 60 * 1e3;
|
|
137459
|
+
var FAILED_RETRY_DELAY_MS = 60 * 1e3;
|
|
137439
137460
|
function projectEnvironmentStateFor(project, stack, updates) {
|
|
137440
137461
|
const current = getProjectEnvironmentState(project, stack);
|
|
137441
137462
|
return getProjectEnvironmentState({
|
|
@@ -137449,6 +137470,7 @@ function projectEnvironmentStateFor(project, stack, updates) {
|
|
|
137449
137470
|
async function ensureEnvironmentReady(opts) {
|
|
137450
137471
|
const contract = resolveStackEnvironmentContract(opts.stack);
|
|
137451
137472
|
const current = getProjectEnvironmentState(opts.project, opts.stack);
|
|
137473
|
+
const retryAtMs = current.nextProvisionRetryAt ? Date.parse(current.nextProvisionRetryAt) : Number.NaN;
|
|
137452
137474
|
if (current.status === "ready") {
|
|
137453
137475
|
return { ready: true, state: current };
|
|
137454
137476
|
}
|
|
@@ -137456,6 +137478,16 @@ async function ensureEnvironmentReady(opts) {
|
|
|
137456
137478
|
const provisioningStartedAt = current.provisioningStartedAt ? Date.parse(current.provisioningStartedAt) : Number.NaN;
|
|
137457
137479
|
const provisioningIsFresh = Number.isFinite(provisioningStartedAt) && Date.now() - provisioningStartedAt < STALE_PROVISIONING_WINDOW_MS;
|
|
137458
137480
|
if (provisioningIsFresh) {
|
|
137481
|
+
await log(opts.workspaceDir, "environment_bootstrap_blocked", {
|
|
137482
|
+
projectSlug: opts.projectSlug,
|
|
137483
|
+
stack: opts.stack,
|
|
137484
|
+
mode: opts.mode,
|
|
137485
|
+
status: current.status,
|
|
137486
|
+
reason: "provisioning_in_progress",
|
|
137487
|
+
provisioningStartedAt: current.provisioningStartedAt ?? null,
|
|
137488
|
+
contractVersion: current.contractVersion ?? contract.version
|
|
137489
|
+
}).catch(() => {
|
|
137490
|
+
});
|
|
137459
137491
|
return { ready: false, state: current };
|
|
137460
137492
|
}
|
|
137461
137493
|
await log(opts.workspaceDir, "environment_bootstrap_retry_scheduled", {
|
|
@@ -137466,7 +137498,18 @@ async function ensureEnvironmentReady(opts) {
|
|
|
137466
137498
|
}).catch(() => {
|
|
137467
137499
|
});
|
|
137468
137500
|
}
|
|
137469
|
-
if (current.status === "failed" && current.nextProvisionRetryAt &&
|
|
137501
|
+
if (current.status === "failed" && current.nextProvisionRetryAt && Number.isFinite(retryAtMs) && retryAtMs > Date.now()) {
|
|
137502
|
+
await log(opts.workspaceDir, "environment_bootstrap_blocked", {
|
|
137503
|
+
projectSlug: opts.projectSlug,
|
|
137504
|
+
stack: opts.stack,
|
|
137505
|
+
mode: opts.mode,
|
|
137506
|
+
status: current.status,
|
|
137507
|
+
reason: "retry_backoff_active",
|
|
137508
|
+
blockedUntil: current.nextProvisionRetryAt,
|
|
137509
|
+
lastProvisionError: current.lastProvisionError ?? null,
|
|
137510
|
+
contractVersion: current.contractVersion ?? contract.version
|
|
137511
|
+
}).catch(() => {
|
|
137512
|
+
});
|
|
137470
137513
|
return { ready: false, state: current };
|
|
137471
137514
|
}
|
|
137472
137515
|
await updateProjectEnvironment(opts.workspaceDir, opts.projectSlug, {
|
|
@@ -137480,6 +137523,8 @@ async function ensureEnvironmentReady(opts) {
|
|
|
137480
137523
|
await log(opts.workspaceDir, "environment_bootstrap_started", {
|
|
137481
137524
|
projectSlug: opts.projectSlug,
|
|
137482
137525
|
stack: opts.stack,
|
|
137526
|
+
mode: opts.mode,
|
|
137527
|
+
previousStatus: current.status,
|
|
137483
137528
|
contractVersion: contract.version
|
|
137484
137529
|
}).catch(() => {
|
|
137485
137530
|
});
|
|
@@ -137505,7 +137550,7 @@ async function ensureEnvironmentReady(opts) {
|
|
|
137505
137550
|
reason: error48 instanceof Error ? error48.message : "environment_bootstrap_failed"
|
|
137506
137551
|
}));
|
|
137507
137552
|
if (!result.ready) {
|
|
137508
|
-
const nextRetryAt = new Date(Date.now() +
|
|
137553
|
+
const nextRetryAt = new Date(Date.now() + FAILED_RETRY_DELAY_MS).toISOString();
|
|
137509
137554
|
const state2 = projectEnvironmentStateFor(opts.project, opts.stack, {
|
|
137510
137555
|
status: "failed",
|
|
137511
137556
|
stack: opts.stack,
|
|
@@ -137526,8 +137571,11 @@ async function ensureEnvironmentReady(opts) {
|
|
|
137526
137571
|
await log(opts.workspaceDir, "environment_bootstrap_retry_scheduled", {
|
|
137527
137572
|
projectSlug: opts.projectSlug,
|
|
137528
137573
|
stack: opts.stack,
|
|
137574
|
+
mode: opts.mode,
|
|
137529
137575
|
nextRetryAt,
|
|
137530
|
-
|
|
137576
|
+
retryDelayMs: FAILED_RETRY_DELAY_MS,
|
|
137577
|
+
reason: state2.lastProvisionError ?? "environment_bootstrap_failed",
|
|
137578
|
+
contractVersion: state2.contractVersion ?? contract.version
|
|
137531
137579
|
}).catch(() => {
|
|
137532
137580
|
});
|
|
137533
137581
|
return { ready: false, state: state2 };
|
|
@@ -137554,6 +137602,9 @@ async function ensureEnvironmentReady(opts) {
|
|
|
137554
137602
|
await log(opts.workspaceDir, "environment_ready_confirmed", {
|
|
137555
137603
|
projectSlug: opts.projectSlug,
|
|
137556
137604
|
stack: opts.stack,
|
|
137605
|
+
mode: opts.mode,
|
|
137606
|
+
previousStatus: current.status,
|
|
137607
|
+
lastProvisionedAt: provisionedAt,
|
|
137557
137608
|
contractVersion: contract.version
|
|
137558
137609
|
}).catch(() => {
|
|
137559
137610
|
});
|
|
@@ -137620,6 +137671,15 @@ async function cleanupExpired(workspaceDir, ttlMs = DEFAULT_TTL_MS2) {
|
|
|
137620
137671
|
|
|
137621
137672
|
// lib/services/tick.ts
|
|
137622
137673
|
init_registry();
|
|
137674
|
+
function classifyEnvironmentGateSkip(state) {
|
|
137675
|
+
if (state.status === "provisioning") return "environment_provisioning_in_progress";
|
|
137676
|
+
if (state.status === "failed") {
|
|
137677
|
+
if (state.nextProvisionRetryAt) return "environment_retry_backoff_active";
|
|
137678
|
+
return "environment_failed";
|
|
137679
|
+
}
|
|
137680
|
+
if (state.status === "pending") return "environment_pending_provisioning";
|
|
137681
|
+
return "environment_not_ready";
|
|
137682
|
+
}
|
|
137623
137683
|
async function projectTick(opts) {
|
|
137624
137684
|
const {
|
|
137625
137685
|
workspaceDir,
|
|
@@ -137859,12 +137919,16 @@ async function projectTick(opts) {
|
|
|
137859
137919
|
runCommand
|
|
137860
137920
|
});
|
|
137861
137921
|
if (!environment.ready) {
|
|
137862
|
-
|
|
137922
|
+
const environmentSkipReason = classifyEnvironmentGateSkip(environment.state);
|
|
137923
|
+
skipped.push({ role, reason: environmentSkipReason });
|
|
137863
137924
|
await log(workspaceDir, "dispatch_blocked_environment_not_ready", {
|
|
137864
137925
|
projectSlug,
|
|
137865
137926
|
role,
|
|
137866
137927
|
issueId: issue2.iid,
|
|
137867
|
-
|
|
137928
|
+
reason: environmentSkipReason,
|
|
137929
|
+
environmentStatus: environment.state.status,
|
|
137930
|
+
nextProvisionRetryAt: environment.state.nextProvisionRetryAt ?? null,
|
|
137931
|
+
lastProvisionError: environment.state.lastProvisionError ?? null
|
|
137868
137932
|
}).catch(() => {
|
|
137869
137933
|
});
|
|
137870
137934
|
continue;
|
|
@@ -140081,14 +140145,16 @@ Erro: ${result.error ?? "erro desconhecido"}`
|
|
|
140081
140145
|
);
|
|
140082
140146
|
}
|
|
140083
140147
|
function registerTelegramBootstrapHook(api, ctx) {
|
|
140148
|
+
const telegramConfig = readFabricaTelegramConfig(ctx.pluginConfig);
|
|
140084
140149
|
const apiRuntime = api.runtime;
|
|
140085
140150
|
const hasGenesis = apiRuntime ? hasGenesisAgent(apiRuntime) : false;
|
|
140086
|
-
|
|
140151
|
+
const usesDedicatedGenesisBootstrap = hasGenesis && telegramConfig.bootstrapDmEnabled && Boolean(telegramConfig.projectsForumChatId);
|
|
140152
|
+
if (!usesDedicatedGenesisBootstrap) {
|
|
140087
140153
|
api.on("before_dispatch", async (event, eventCtx) => {
|
|
140088
140154
|
const hookCtx = eventCtx;
|
|
140089
140155
|
if (hookCtx.channelId !== "telegram") return void 0;
|
|
140090
|
-
const
|
|
140091
|
-
if (!
|
|
140156
|
+
const telegramConfig2 = readFabricaTelegramConfig(ctx.pluginConfig);
|
|
140157
|
+
if (!telegramConfig2.bootstrapDmEnabled) return void 0;
|
|
140092
140158
|
const conversationId = resolveTelegramBootstrapDmConversationIdFromHookContext(hookCtx);
|
|
140093
140159
|
if (!conversationId) return void 0;
|
|
140094
140160
|
const dispatchEvent = event;
|
|
@@ -140172,11 +140238,11 @@ function registerTelegramBootstrapHook(api, ctx) {
|
|
|
140172
140238
|
}
|
|
140173
140239
|
});
|
|
140174
140240
|
}
|
|
140175
|
-
if (
|
|
140241
|
+
if (usesDedicatedGenesisBootstrap) return;
|
|
140176
140242
|
api.on("message_received", async (event, eventCtx) => {
|
|
140177
140243
|
if (eventCtx.channelId !== "telegram") return;
|
|
140178
|
-
const
|
|
140179
|
-
if (!
|
|
140244
|
+
const telegramConfig2 = readFabricaTelegramConfig(ctx.pluginConfig);
|
|
140245
|
+
if (!telegramConfig2.bootstrapDmEnabled) return;
|
|
140180
140246
|
const rawConversationId = String(eventCtx.conversationId ?? "").trim();
|
|
140181
140247
|
const content = String(event.content ?? "").trim();
|
|
140182
140248
|
if (!rawConversationId || !content) return;
|
|
@@ -142373,6 +142439,8 @@ function createSetupTool(ctx) {
|
|
|
142373
142439
|
${written.map((f3) => ` ${f3}`).join("\n")}` : "All files already exist \u2014 nothing to write."
|
|
142374
142440
|
});
|
|
142375
142441
|
}
|
|
142442
|
+
const telegramConfig = readFabricaTelegramConfig(ctx.pluginConfig);
|
|
142443
|
+
const shouldEnsureGenesis = telegramConfig.bootstrapDmEnabled && Boolean(telegramConfig.projectsForumChatId);
|
|
142376
142444
|
const result = await runSetup({
|
|
142377
142445
|
runtime: ctx.runtime,
|
|
142378
142446
|
newAgentName: params.newAgentName,
|
|
@@ -142382,7 +142450,9 @@ ${written.map((f3) => ` ${f3}`).join("\n")}` : "All files already exist \u2014
|
|
|
142382
142450
|
workspacePath: params.newAgentName ? void 0 : toolCtx.workspaceDir,
|
|
142383
142451
|
models: params.models,
|
|
142384
142452
|
projectExecution: params.projectExecution,
|
|
142385
|
-
runCommand: ctx.runCommand
|
|
142453
|
+
runCommand: ctx.runCommand,
|
|
142454
|
+
ensureGenesis: shouldEnsureGenesis,
|
|
142455
|
+
forumGroupId: telegramConfig.projectsForumChatId
|
|
142386
142456
|
});
|
|
142387
142457
|
const lines = [
|
|
142388
142458
|
result.agentCreated ? `Agent "${result.agentId}" created` : `Configured "${result.agentId}"`,
|
|
@@ -142536,16 +142606,19 @@ Call \`setup\` with the collected answers:
|
|
|
142536
142606
|
After setup completes, explain the current operating model:
|
|
142537
142607
|
|
|
142538
142608
|
\u{1F4F1} **Telegram Guidance:**
|
|
142539
|
-
Fabrica uses:
|
|
142609
|
+
Fabrica uses the following official path:
|
|
142540
142610
|
1. **DM with the bot** for new-project bootstrap and short clarifications
|
|
142541
142611
|
2. **One forum group for projects** with **one topic per project**
|
|
142542
|
-
3. **One separate ops group** for health/cron/status
|
|
142543
142612
|
|
|
142544
|
-
**
|
|
142613
|
+
**Minimum recommended setup:**
|
|
142545
142614
|
1. Keep the bot reachable in DM
|
|
142546
142615
|
2. Add the bot to the projects forum group
|
|
142547
142616
|
3. Ensure the bot can create/manage topics there
|
|
142548
|
-
4.
|
|
142617
|
+
4. Set 'plugins.entries.fabrica.config.telegram.projectsForumChatId' to that forum group ID
|
|
142618
|
+
|
|
142619
|
+
**Optional / advanced:**
|
|
142620
|
+
- 'projectsForumAccountId' if a specific Telegram account should own forum actions
|
|
142621
|
+
- 'opsChatId' only if you want a separate ops-only route; it is not required for the core product flow
|
|
142549
142622
|
|
|
142550
142623
|
**Step 5: Project Registration**
|
|
142551
142624
|
Explain that the canonical path for new projects is:
|
|
@@ -142562,10 +142635,10 @@ Manual \`project_register\` remains available for admin recovery and exceptional
|
|
|
142562
142635
|
**Step 6: Workflow Overview**
|
|
142563
142636
|
After project registration, briefly tell the user about their active workflow:
|
|
142564
142637
|
|
|
142565
|
-
- **Review policy**:
|
|
142566
|
-
- **Test phase**:
|
|
142567
|
-
- **Customization**: They can change the review policy (human
|
|
142568
|
-
- Say: "Autonomous projects created from DM use **agent review**
|
|
142638
|
+
- **Review policy**: autonomous DM-created projects use **agent review** by default as a quality guardrail.
|
|
142639
|
+
- **Test phase**: the current workflow still defaults to 'testPolicy: skip' unless explicitly enabled. Explain this as the current operational default, not the quality ideal of the product.
|
|
142640
|
+
- **Customization**: They can change the review policy ('human', 'agent', 'skip'), enable testing ('testPolicy: agent'), or override settings per project. Point them to 'workflow.yaml' in the Fabrica workspace data directory.
|
|
142641
|
+
- Say: "Autonomous projects created from DM use **agent review** by default. Testing is still skipped by default in the current workflow unless you enable it, so if you need stricter QA by default you should change 'testPolicy' in your workflow.yaml."
|
|
142569
142642
|
|
|
142570
142643
|
## Guidelines
|
|
142571
142644
|
- Be conversational and friendly. Ask one question at a time.
|
|
@@ -144163,19 +144236,20 @@ async function checkConfigLoads(workspacePath) {
|
|
|
144163
144236
|
}
|
|
144164
144237
|
}
|
|
144165
144238
|
function checkTelegramBootstrapConfig(pluginConfig) {
|
|
144166
|
-
const
|
|
144167
|
-
|
|
144239
|
+
const rawTelegram = pluginConfig?.telegram ?? {};
|
|
144240
|
+
const telegram = readFabricaTelegramConfig(pluginConfig);
|
|
144241
|
+
if (rawTelegram.bootstrapDmEnabled === false) {
|
|
144168
144242
|
return {
|
|
144169
144243
|
name: "config:telegram-bootstrap",
|
|
144170
144244
|
severity: "ok",
|
|
144171
|
-
message: "Telegram DM bootstrap is disabled (bootstrapDmEnabled
|
|
144245
|
+
message: "Telegram DM bootstrap is disabled (bootstrapDmEnabled=false)"
|
|
144172
144246
|
};
|
|
144173
144247
|
}
|
|
144174
|
-
if (!telegram
|
|
144248
|
+
if (!telegram.projectsForumChatId) {
|
|
144175
144249
|
return {
|
|
144176
144250
|
name: "config:telegram-bootstrap",
|
|
144177
144251
|
severity: "warn",
|
|
144178
|
-
message: "Telegram DM bootstrap is
|
|
144252
|
+
message: "Telegram DM bootstrap is active by default but projectsForumChatId is not configured in plugins.entries.fabrica.config.telegram \u2014 the official DM \u2192 topic flow will fail at runtime"
|
|
144179
144253
|
};
|
|
144180
144254
|
}
|
|
144181
144255
|
return {
|
|
@@ -144442,13 +144516,21 @@ async function runSecurityDoctor(openclawHome) {
|
|
|
144442
144516
|
severity: residualPlugins.length > 0 ? "warn" : "ok",
|
|
144443
144517
|
message: residualPlugins.length > 0 ? `Residual plugins still enabled: ${residualPlugins.join(", ")}` : "No residual non-essential plugins enabled"
|
|
144444
144518
|
});
|
|
144445
|
-
const
|
|
144519
|
+
const legacyRouterBinding = (config2.bindings ?? []).find(
|
|
144446
144520
|
(binding) => binding.agentId === "genesis-router" && binding.match?.channel === "telegram"
|
|
144447
144521
|
);
|
|
144448
144522
|
checks.push({
|
|
144449
144523
|
name: "bindings:telegram-router",
|
|
144450
|
-
severity:
|
|
144451
|
-
message:
|
|
144524
|
+
severity: legacyRouterBinding ? "warn" : "ok",
|
|
144525
|
+
message: legacyRouterBinding ? "Telegram is still bound to legacy genesis-router" : "Telegram is not bound to legacy genesis-router"
|
|
144526
|
+
});
|
|
144527
|
+
const genesisBinding = (config2.bindings ?? []).find(
|
|
144528
|
+
(binding) => binding.agentId === "genesis" && binding.match?.channel === "telegram" && !binding.match?.peer
|
|
144529
|
+
);
|
|
144530
|
+
checks.push({
|
|
144531
|
+
name: "bindings:telegram-genesis",
|
|
144532
|
+
severity: genesisBinding ? "ok" : "warn",
|
|
144533
|
+
message: genesisBinding ? "Genesis agent owns the channel-wide Telegram DM binding" : "Genesis agent does not own a channel-wide Telegram binding"
|
|
144452
144534
|
});
|
|
144453
144535
|
try {
|
|
144454
144536
|
await fs42.access(checklistPath);
|
|
@@ -144763,13 +144845,17 @@ function registerCli(program, ctx) {
|
|
|
144763
144845
|
}
|
|
144764
144846
|
if (Object.keys(roleModels).length > 0) models[role] = roleModels;
|
|
144765
144847
|
}
|
|
144848
|
+
const telegramConfig = readFabricaTelegramConfig(ctx.pluginConfig);
|
|
144849
|
+
const shouldEnsureGenesis = telegramConfig.bootstrapDmEnabled && Boolean(telegramConfig.projectsForumChatId);
|
|
144766
144850
|
const result = await runSetup({
|
|
144767
144851
|
runtime: ctx.runtime,
|
|
144768
144852
|
newAgentName: opts.newAgent,
|
|
144769
144853
|
agentId: opts.agent,
|
|
144770
144854
|
workspacePath: opts.workspace,
|
|
144771
144855
|
models: Object.keys(models).length > 0 ? models : void 0,
|
|
144772
|
-
runCommand: ctx.runCommand
|
|
144856
|
+
runCommand: ctx.runCommand,
|
|
144857
|
+
ensureGenesis: shouldEnsureGenesis,
|
|
144858
|
+
forumGroupId: telegramConfig.projectsForumChatId
|
|
144773
144859
|
});
|
|
144774
144860
|
if (result.agentCreated) {
|
|
144775
144861
|
console.log(`Agent "${result.agentId}" created`);
|
|
@@ -144791,9 +144877,15 @@ function registerCli(program, ctx) {
|
|
|
144791
144877
|
}
|
|
144792
144878
|
}
|
|
144793
144879
|
console.log("\nDone! Next steps:");
|
|
144794
|
-
|
|
144795
|
-
|
|
144796
|
-
|
|
144880
|
+
if (shouldEnsureGenesis) {
|
|
144881
|
+
console.log(" 1. Keep the bot reachable in DM");
|
|
144882
|
+
console.log(" 2. Add the bot to the projects forum group and allow topic creation");
|
|
144883
|
+
console.log(" 3. Send the project idea to the bot in DM to bootstrap the project automatically");
|
|
144884
|
+
} else {
|
|
144885
|
+
console.log(" 1. Run `openclaw fabrica doctor workspace --workspace <path>` to confirm workspace readiness");
|
|
144886
|
+
console.log(" 2. If you want the official Telegram DM \u2192 topic flow, set plugins.entries.fabrica.config.telegram.projectsForumChatId");
|
|
144887
|
+
console.log(" 3. Re-run `openclaw fabrica setup` after the Telegram forum config is in place");
|
|
144888
|
+
}
|
|
144797
144889
|
});
|
|
144798
144890
|
const doctor = fabrica.command("doctor").description("Diagnose workspace integrity and optionally auto-fix issues");
|
|
144799
144891
|
doctor.command("workspace").description("Diagnose workspace integrity and optionally auto-fix issues").option("-w, --workspace <path>", "Workspace directory").option("--fix", "Apply fixes for detected issues").action(async (opts) => {
|
|
@@ -146107,7 +146199,7 @@ var plugin = {
|
|
|
146107
146199
|
},
|
|
146108
146200
|
telegram: {
|
|
146109
146201
|
type: "object",
|
|
146110
|
-
description: "Telegram routing for DM
|
|
146202
|
+
description: "Telegram routing for the official DM \u2192 project-topic flow. Core settings are DM bootstrap and the projects forum chat.",
|
|
146111
146203
|
properties: {
|
|
146112
146204
|
bootstrapDmEnabled: {
|
|
146113
146205
|
type: "boolean",
|
|
@@ -146116,15 +146208,15 @@ var plugin = {
|
|
|
146116
146208
|
},
|
|
146117
146209
|
projectsForumChatId: {
|
|
146118
146210
|
type: "string",
|
|
146119
|
-
description: "Telegram forum group chat ID where Fabrica creates one topic per project."
|
|
146211
|
+
description: "Telegram forum group chat ID where Fabrica creates one topic per project. This is the key Telegram setting for the official Fabrica flow."
|
|
146120
146212
|
},
|
|
146121
146213
|
projectsForumAccountId: {
|
|
146122
146214
|
type: "string",
|
|
146123
|
-
description: "Optional Telegram account ID to use when creating
|
|
146215
|
+
description: "Optional Telegram account ID to use when creating or sending project forum topic updates."
|
|
146124
146216
|
},
|
|
146125
146217
|
opsChatId: {
|
|
146126
146218
|
type: "string",
|
|
146127
|
-
description: "Telegram group/chat ID for
|
|
146219
|
+
description: "Optional Telegram group/chat ID for separate ops-only notifications. Not required for the core DM \u2192 topic product flow."
|
|
146128
146220
|
}
|
|
146129
146221
|
}
|
|
146130
146222
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mestreyoda/fabrica",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.16",
|
|
4
4
|
"description": "Autonomous software engineering pipeline for OpenClaw. Turns ideas into deployed code via intake, dispatch, review, test, and merge.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -18,8 +18,6 @@
|
|
|
18
18
|
"genesis/",
|
|
19
19
|
"fabrica.manifest.json",
|
|
20
20
|
"openclaw.plugin.json",
|
|
21
|
-
"ARCHITECTURE.md",
|
|
22
|
-
"CHANGELOG.md",
|
|
23
21
|
"README.md",
|
|
24
22
|
"LICENSE"
|
|
25
23
|
],
|
|
@@ -50,7 +48,7 @@
|
|
|
50
48
|
},
|
|
51
49
|
"repository": {
|
|
52
50
|
"type": "git",
|
|
53
|
-
"url": "https://github.com/MestreY0d4-Uninter/fabrica.git"
|
|
51
|
+
"url": "git+https://github.com/MestreY0d4-Uninter/fabrica.git"
|
|
54
52
|
},
|
|
55
53
|
"homepage": "https://github.com/MestreY0d4-Uninter/fabrica#readme",
|
|
56
54
|
"bugs": {
|
package/ARCHITECTURE.md
DELETED
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
# Architecture
|
|
2
|
-
|
|
3
|
-
## Core shape
|
|
4
|
-
|
|
5
|
-
Fabrica is implemented as a local OpenClaw plugin with the local repository as
|
|
6
|
-
its source of truth.
|
|
7
|
-
|
|
8
|
-
Main areas:
|
|
9
|
-
|
|
10
|
-
- `lib/intake`
|
|
11
|
-
Intake, target resolution, impact analysis, task creation and triage.
|
|
12
|
-
- `lib/github`
|
|
13
|
-
GitHub App auth, webhook ingestion, event store, PR binding, quality gate and
|
|
14
|
-
governance.
|
|
15
|
-
- `lib/services`
|
|
16
|
-
Pipeline, heartbeat, queue scans and workflow execution helpers.
|
|
17
|
-
- `lib/machines`
|
|
18
|
-
`FabricaRunMachine` and `LifecycleMachine` for explicit state transitions.
|
|
19
|
-
- `lib/observability`
|
|
20
|
-
Pino logging, correlation context and OpenTelemetry spans.
|
|
21
|
-
- `lib/dispatch`
|
|
22
|
-
DM bootstrap, Telegram topic routing, worker notifications and attachment hooks.
|
|
23
|
-
- `lib/telegram`
|
|
24
|
-
Telegram config resolution and topic creation services.
|
|
25
|
-
- `defaults`
|
|
26
|
-
Packaged assets and workflow defaults that ship with the plugin.
|
|
27
|
-
- `genesis`
|
|
28
|
-
Packaged runtime assets still used by the plugin during the migration away
|
|
29
|
-
from older shell-driven flows.
|
|
30
|
-
|
|
31
|
-
## Runtime model
|
|
32
|
-
|
|
33
|
-
## Telegram routing model
|
|
34
|
-
|
|
35
|
-
New project intake is DM-first. The Fabrica bot accepts a new-project request in
|
|
36
|
-
Telegram DM, asks for missing essentials there if needed, and only creates the
|
|
37
|
-
project topic when the intake is ready to register. For greenfield projects,
|
|
38
|
-
repo provisioning now happens in the TS intake path before registration and
|
|
39
|
-
issue creation.
|
|
40
|
-
|
|
41
|
-
The canonic route identity for Telegram-backed projects is:
|
|
42
|
-
|
|
43
|
-
`channel=telegram + channelId + messageThreadId`
|
|
44
|
-
|
|
45
|
-
This avoids collisions between multiple projects inside the same Telegram forum
|
|
46
|
-
group. After registration:
|
|
47
|
-
|
|
48
|
-
- the project topic becomes the primary route for project messages
|
|
49
|
-
- follow-ups inside that topic resolve the exact project
|
|
50
|
-
- worker notifications and project lifecycle updates publish back to that topic
|
|
51
|
-
- ops alerts stay in the separate ops group
|
|
52
|
-
|
|
53
|
-
The hot path for GitHub is:
|
|
54
|
-
|
|
55
|
-
`webhook -> event store -> FabricaRun -> Quality Gate -> artifactOfRecord -> done`
|
|
56
|
-
|
|
57
|
-
Important invariants:
|
|
58
|
-
|
|
59
|
-
- a cycle never closes with an open canonical PR
|
|
60
|
-
- `Done` requires `artifactOfRecord`
|
|
61
|
-
- duplicate GitHub deliveries must not duplicate effects
|
|
62
|
-
- force-push updates the canonical binding instead of spawning duplicate runs
|
|
63
|
-
|
|
64
|
-
## Installation model
|
|
65
|
-
|
|
66
|
-
Fabrica is distributed as a self-contained OpenClaw plugin package.
|
|
67
|
-
|
|
68
|
-
The supported operator path is:
|
|
69
|
-
|
|
70
|
-
```bash
|
|
71
|
-
openclaw plugins install @mestreyoda/fabrica
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
The installed extension must be loadable in isolation. Fabrica may depend on
|
|
75
|
-
OpenClaw only through the plugin host ABI and runtime objects passed by the
|
|
76
|
-
host. It must not require manual symlinks, local `npm install`, or host-global
|
|
77
|
-
module resolution to load.
|
|
78
|
-
|
|
79
|
-
External credentials and routes such as GitHub auth, Telegram chat IDs, and
|
|
80
|
-
webhook secrets are operational configuration, not installation dependencies.
|
|
81
|
-
Fabrica's `doctor` and `setup` flows guide and validate that operational
|
|
82
|
-
configuration where applicable.
|
|
83
|
-
|
|
84
|
-
## Operational notes
|
|
85
|
-
|
|
86
|
-
- Gateway runtime is managed by the OpenClaw systemd service.
|
|
87
|
-
- GitHub webhook ingress is protected by GitHub signature validation inside the
|
|
88
|
-
plugin; the route itself must remain reachable without gateway bearer auth.
|
|
89
|
-
- GitHub App and webhook credentials are expected to come from the Fabrica
|
|
90
|
-
plugin config (`openclaw.json`) using direct values and credential file paths;
|
|
91
|
-
legacy env-based fields remain only as compatibility fallback.
|
|
92
|
-
- Structured logs and OpenTelemetry spans are emitted by the plugin itself.
|
|
93
|
-
- Security validation lives in `openclaw fabrica doctor security --json`.
|
package/CHANGELOG.md
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
# Changelog
|
|
2
|
-
|
|
3
|
-
## 0.2.15 - 2026-04-03
|
|
4
|
-
|
|
5
|
-
- Hardened Telegram DM intake around durable `pending_classify` / `classifying` recovery, newer-attempt ownership, and explicit late-classify reconciliation.
|
|
6
|
-
- Added runtime-aware DM claiming via `before_dispatch` plus short-lived message/conversation guards so Telegram prompts stay inside Fabrica instead of leaking to the generic OpenClaw agent.
|
|
7
|
-
- Fixed greenfield scaffold canonical repo path handling so `metadata.repo_path` / `scaffold_plan.repo_local` survive all the way into `scaffold-project.sh` and published genesis assets.
|
|
8
|
-
- Tightened bootstrap and register fail-closed behavior for unsupported stacks and missing materialized repositories, preventing half-registered validation projects.
|
|
9
|
-
- Reset and revalidated the temporary Telegram validation harness, including a reusable runner path and regression coverage for read/wait flows.
|
|
10
|
-
- Extended regression coverage for Telegram bootstrap recovery, scaffold path ownership, classify-step typing, and end-to-end hot-path stability.
|
|
11
|
-
|
|
12
|
-
## 0.2.14 - 2026-04-02
|
|
13
|
-
|
|
14
|
-
- Added a stack-aware environment gate so developer and tester pickup only start after project environments are provisioned and marked ready.
|
|
15
|
-
- Hardened Python stack bootstrap around durable environment state, retry scheduling, and stale provisioning recovery without `sudo`.
|
|
16
|
-
- Reworked worker recovery so observable activity without a canonical result enters bounded completion recovery instead of immediately corrupting dispatch health.
|
|
17
|
-
- Made heartbeat distinguish accepted-but-idle dispatches, inconclusive completion, terminal sessions, and true dead sessions with cycle-aware ownership checks.
|
|
18
|
-
- Added explicit timeline events for reviewer outcomes and worker recovery exhaustion, with cycle-aware dedupe and corrected destination-state messaging.
|
|
19
|
-
- Preserved reviewer notification routing through plugin notification config instead of bypassing runtime settings.
|
|
20
|
-
- Extended regression coverage for environment provisioning, gateway session transcript activity, heartbeat recovery, reviewer notifications, and end-to-end hot-path orchestration.
|
|
21
|
-
|
|
22
|
-
## 0.2.13 - 2026-03-31
|
|
23
|
-
|
|
24
|
-
- Disabled automatic pretty logging on TTY so the plugin no longer depends on `pino-pretty` during load.
|
|
25
|
-
- Added a safe one-shot fallback to structured logs when pretty transport resolution or initialization fails.
|
|
26
|
-
- Added logger transport regression coverage and promoted it into the hot-path test lane.
|
|
27
|
-
|
|
28
|
-
## 0.2.12 - 2026-03-31
|
|
29
|
-
|
|
30
|
-
- Made the published plugin self-contained by replacing remaining runtime helper imports from `openclaw/plugin-sdk`.
|
|
31
|
-
- Added release gates for runtime-boundary and isolated installability verification, with fail-closed behavior, timeouts, and cleanup.
|
|
32
|
-
- Documented the real install contract and workspace-scoped operational setup flow in the package README and architecture notes.
|
|
33
|
-
- Aligned local deploy to the same contract by removing the extension `node_modules` symlink fallback.
|
|
34
|
-
|
|
35
|
-
## 0.2.11 - 2026-03-31
|
|
36
|
-
|
|
37
|
-
- Unified reviewer completion around the canonical `Review result: APPROVE|REJECT` contract.
|
|
38
|
-
- Hardened dispatch identity and reduced heartbeat progression to repair-oriented behavior.
|
|
39
|
-
- Enforced canonical PR selection across reviewer/tester and queue-side flows.
|
|
40
|
-
- Preserved and hardened the Telegram three-plane model: DM bootstrap, project forum topics, and ops routing.
|
|
41
|
-
- Restored confidence lanes for release verification with explicit `test:unit`, `test:e2e`, and `test:hot-path`.
|
|
42
|
-
- Aligned plugin config governance and observability surfaces so runtime knobs are real and misleading signals no longer present themselves as authoritative.
|