@pixelbyte-software/pixcode 1.42.5 → 1.44.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/dist/assets/index-B-_FofJ_.css +32 -0
- package/dist/assets/{index-nefOyhzb.js → index-BsxRTx-l.js} +142 -140
- package/dist/index.html +2 -2
- package/dist-server/server/index.js +6 -0
- package/dist-server/server/index.js.map +1 -1
- package/dist-server/server/modules/orchestration/index.js +1 -0
- package/dist-server/server/modules/orchestration/index.js.map +1 -1
- package/dist-server/server/modules/orchestration/workflows/approval-queue.js +72 -0
- package/dist-server/server/modules/orchestration/workflows/approval-queue.js.map +1 -0
- package/dist-server/server/modules/orchestration/workflows/workflow-runner.js +25 -0
- package/dist-server/server/modules/orchestration/workflows/workflow-runner.js.map +1 -1
- package/dist-server/server/modules/orchestration/workflows/workflow.routes.js +87 -0
- package/dist-server/server/modules/orchestration/workflows/workflow.routes.js.map +1 -1
- package/dist-server/server/routes/production-agent-loop.js +67 -0
- package/dist-server/server/routes/production-agent-loop.js.map +1 -0
- package/dist-server/server/routes/public-api.js +7 -1
- package/dist-server/server/routes/public-api.js.map +1 -1
- package/dist-server/server/routes/remote.js +18 -0
- package/dist-server/server/routes/remote.js.map +1 -1
- package/dist-server/server/routes/webhooks.js +53 -0
- package/dist-server/server/routes/webhooks.js.map +1 -0
- package/dist-server/server/services/control-room.js +89 -0
- package/dist-server/server/services/control-room.js.map +1 -0
- package/dist-server/server/services/production-agent-loop.js +233 -0
- package/dist-server/server/services/production-agent-loop.js.map +1 -0
- package/dist-server/server/services/public-api-manifest.js +96 -0
- package/dist-server/server/services/public-api-manifest.js.map +1 -1
- package/dist-server/server/services/telegram/control-center.js +110 -0
- package/dist-server/server/services/telegram/control-center.js.map +1 -1
- package/dist-server/server/services/telegram/translations.js +24 -2
- package/dist-server/server/services/telegram/translations.js.map +1 -1
- package/dist-server/server/services/webhooks.js +198 -0
- package/dist-server/server/services/webhooks.js.map +1 -0
- package/package.json +1 -1
- package/scripts/smoke/v143-remote-control.mjs +76 -0
- package/scripts/smoke/v144-production-loop.mjs +47 -0
- package/server/index.js +8 -0
- package/server/modules/orchestration/index.ts +4 -0
- package/server/modules/orchestration/workflows/approval-queue.ts +106 -0
- package/server/modules/orchestration/workflows/workflow-runner.ts +25 -0
- package/server/modules/orchestration/workflows/workflow.routes.ts +95 -0
- package/server/routes/production-agent-loop.js +90 -0
- package/server/routes/public-api.js +14 -1
- package/server/routes/remote.js +22 -0
- package/server/routes/webhooks.js +63 -0
- package/server/services/control-room.js +102 -0
- package/server/services/production-agent-loop.js +248 -0
- package/server/services/public-api-manifest.js +98 -0
- package/server/services/telegram/control-center.js +113 -0
- package/server/services/telegram/translations.js +24 -2
- package/server/services/webhooks.js +216 -0
- package/dist/assets/index-CHa1760s.css +0 -32
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
|
|
3
|
+
import { appConfigDb } from '../database/db.js';
|
|
4
|
+
|
|
5
|
+
const CONFIG_KEY = 'webhooks';
|
|
6
|
+
const MAX_ATTEMPTS = 2;
|
|
7
|
+
|
|
8
|
+
export const PIXCODE_WEBHOOK_EVENT_TYPES = [
|
|
9
|
+
'run.started',
|
|
10
|
+
'run.completed',
|
|
11
|
+
'run.failed',
|
|
12
|
+
'run.canceled',
|
|
13
|
+
'file.changed',
|
|
14
|
+
'approval.needed',
|
|
15
|
+
'approval.resolved',
|
|
16
|
+
'live_view.started',
|
|
17
|
+
'live_view.failed',
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
function nowIso() {
|
|
21
|
+
return new Date().toISOString();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function readStore() {
|
|
25
|
+
const raw = appConfigDb.get(CONFIG_KEY);
|
|
26
|
+
if (!raw) return { version: 1, webhooks: [] };
|
|
27
|
+
try {
|
|
28
|
+
const parsed = JSON.parse(raw);
|
|
29
|
+
return {
|
|
30
|
+
version: 1,
|
|
31
|
+
webhooks: Array.isArray(parsed?.webhooks) ? parsed.webhooks.map(normalizeWebhook).filter(Boolean) : [],
|
|
32
|
+
};
|
|
33
|
+
} catch {
|
|
34
|
+
return { version: 1, webhooks: [] };
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function writeStore(store) {
|
|
39
|
+
appConfigDb.set(CONFIG_KEY, JSON.stringify({
|
|
40
|
+
version: 1,
|
|
41
|
+
webhooks: store.webhooks.map(normalizeWebhook).filter(Boolean),
|
|
42
|
+
}));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function normalizeUrl(value) {
|
|
46
|
+
const raw = typeof value === 'string' ? value.trim() : '';
|
|
47
|
+
if (!raw) throw new Error('Webhook URL is required.');
|
|
48
|
+
const parsed = new URL(raw);
|
|
49
|
+
if (!['http:', 'https:'].includes(parsed.protocol)) {
|
|
50
|
+
throw new Error('Webhook URL must use http or https.');
|
|
51
|
+
}
|
|
52
|
+
parsed.hash = '';
|
|
53
|
+
return parsed.toString();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function normalizeEvents(events) {
|
|
57
|
+
if (!Array.isArray(events) || events.length === 0) return ['run.completed', 'run.failed', 'approval.needed'];
|
|
58
|
+
return Array.from(new Set(events
|
|
59
|
+
.filter((event) => PIXCODE_WEBHOOK_EVENT_TYPES.includes(event))
|
|
60
|
+
)).sort();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function normalizeWebhook(input) {
|
|
64
|
+
if (!input || typeof input !== 'object') return null;
|
|
65
|
+
const id = typeof input.id === 'string' && input.id ? input.id : crypto.randomUUID();
|
|
66
|
+
const name = typeof input.name === 'string' && input.name.trim() ? input.name.trim() : 'Pixcode webhook';
|
|
67
|
+
const url = normalizeUrl(input.url);
|
|
68
|
+
const secret = typeof input.secret === 'string' && input.secret.trim()
|
|
69
|
+
? input.secret.trim()
|
|
70
|
+
: crypto.randomBytes(24).toString('hex');
|
|
71
|
+
return {
|
|
72
|
+
id,
|
|
73
|
+
name,
|
|
74
|
+
url,
|
|
75
|
+
secret,
|
|
76
|
+
enabled: input.enabled !== false,
|
|
77
|
+
events: normalizeEvents(input.events),
|
|
78
|
+
createdAt: typeof input.createdAt === 'string' ? input.createdAt : nowIso(),
|
|
79
|
+
updatedAt: typeof input.updatedAt === 'string' ? input.updatedAt : nowIso(),
|
|
80
|
+
lastDelivery: input.lastDelivery && typeof input.lastDelivery === 'object' ? input.lastDelivery : null,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function publicWebhook(webhook) {
|
|
85
|
+
return {
|
|
86
|
+
id: webhook.id,
|
|
87
|
+
name: webhook.name,
|
|
88
|
+
url: webhook.url,
|
|
89
|
+
enabled: webhook.enabled,
|
|
90
|
+
events: webhook.events,
|
|
91
|
+
createdAt: webhook.createdAt,
|
|
92
|
+
updatedAt: webhook.updatedAt,
|
|
93
|
+
secretPresent: Boolean(webhook.secret),
|
|
94
|
+
lastDelivery: webhook.lastDelivery,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function listWebhooks() {
|
|
99
|
+
return readStore().webhooks.map(publicWebhook);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function upsertWebhook(input = {}) {
|
|
103
|
+
const store = readStore();
|
|
104
|
+
const existing = typeof input.id === 'string'
|
|
105
|
+
? store.webhooks.find((webhook) => webhook.id === input.id)
|
|
106
|
+
: null;
|
|
107
|
+
const webhook = normalizeWebhook({
|
|
108
|
+
...existing,
|
|
109
|
+
...input,
|
|
110
|
+
id: existing?.id ?? input.id,
|
|
111
|
+
secret: input.secret === undefined ? existing?.secret : input.secret,
|
|
112
|
+
createdAt: existing?.createdAt,
|
|
113
|
+
updatedAt: nowIso(),
|
|
114
|
+
});
|
|
115
|
+
if (!webhook) throw new Error('Invalid webhook payload.');
|
|
116
|
+
const next = store.webhooks.filter((candidate) => candidate.id !== webhook.id);
|
|
117
|
+
next.push(webhook);
|
|
118
|
+
writeStore({ ...store, webhooks: next });
|
|
119
|
+
return publicWebhook(webhook);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function deleteWebhook(webhookId) {
|
|
123
|
+
const store = readStore();
|
|
124
|
+
const next = store.webhooks.filter((webhook) => webhook.id !== webhookId);
|
|
125
|
+
if (next.length === store.webhooks.length) return false;
|
|
126
|
+
writeStore({ ...store, webhooks: next });
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function signPayload(secret, body) {
|
|
131
|
+
return crypto.createHmac('sha256', secret).update(body).digest('hex');
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function deliveryPayload(event) {
|
|
135
|
+
return {
|
|
136
|
+
id: crypto.randomUUID(),
|
|
137
|
+
protocol: 'pixcode.webhook.v1',
|
|
138
|
+
emittedAt: nowIso(),
|
|
139
|
+
event,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async function deliverToWebhook(webhook, event) {
|
|
144
|
+
const payload = deliveryPayload(event);
|
|
145
|
+
const body = JSON.stringify(payload);
|
|
146
|
+
const signature = signPayload(webhook.secret, body);
|
|
147
|
+
let lastError = null;
|
|
148
|
+
|
|
149
|
+
for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt += 1) {
|
|
150
|
+
try {
|
|
151
|
+
const response = await fetch(webhook.url, {
|
|
152
|
+
method: 'POST',
|
|
153
|
+
headers: {
|
|
154
|
+
'Content-Type': 'application/json',
|
|
155
|
+
'X-Pixcode-Event': event.type,
|
|
156
|
+
'X-Pixcode-Delivery': payload.id,
|
|
157
|
+
'X-Pixcode-Signature-256': `sha256=${signature}`,
|
|
158
|
+
},
|
|
159
|
+
body,
|
|
160
|
+
});
|
|
161
|
+
const result = {
|
|
162
|
+
ok: response.ok,
|
|
163
|
+
status: response.status,
|
|
164
|
+
attempt,
|
|
165
|
+
eventType: event.type,
|
|
166
|
+
deliveredAt: nowIso(),
|
|
167
|
+
};
|
|
168
|
+
return result;
|
|
169
|
+
} catch (error) {
|
|
170
|
+
lastError = {
|
|
171
|
+
ok: false,
|
|
172
|
+
attempt,
|
|
173
|
+
eventType: event.type,
|
|
174
|
+
deliveredAt: nowIso(),
|
|
175
|
+
error: error?.message || String(error),
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return lastError;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function recordDelivery(webhookId, delivery) {
|
|
184
|
+
const store = readStore();
|
|
185
|
+
const next = store.webhooks.map((webhook) => (
|
|
186
|
+
webhook.id === webhookId ? { ...webhook, lastDelivery: delivery, updatedAt: nowIso() } : webhook
|
|
187
|
+
));
|
|
188
|
+
writeStore({ ...store, webhooks: next });
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export async function deliverWebhookEvent(event) {
|
|
192
|
+
const normalized = {
|
|
193
|
+
type: event?.type,
|
|
194
|
+
payload: event?.payload && typeof event.payload === 'object' ? event.payload : {},
|
|
195
|
+
};
|
|
196
|
+
if (!PIXCODE_WEBHOOK_EVENT_TYPES.includes(normalized.type)) {
|
|
197
|
+
return { delivered: 0, skipped: true, reason: 'unsupported_event' };
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const webhooks = readStore().webhooks.filter((webhook) =>
|
|
201
|
+
webhook.enabled && webhook.events.includes(normalized.type)
|
|
202
|
+
);
|
|
203
|
+
const deliveries = [];
|
|
204
|
+
for (const webhook of webhooks) {
|
|
205
|
+
const delivery = await deliverToWebhook(webhook, normalized);
|
|
206
|
+
recordDelivery(webhook.id, delivery);
|
|
207
|
+
deliveries.push({ webhookId: webhook.id, ...delivery });
|
|
208
|
+
}
|
|
209
|
+
return { delivered: deliveries.length, deliveries };
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export function dispatchWebhookEvent(event) {
|
|
213
|
+
deliverWebhookEvent(event).catch((error) => {
|
|
214
|
+
console.warn('[webhooks] delivery failed:', error?.message || error);
|
|
215
|
+
});
|
|
216
|
+
}
|