@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.
Files changed (52) hide show
  1. package/dist/assets/index-B-_FofJ_.css +32 -0
  2. package/dist/assets/{index-nefOyhzb.js → index-BsxRTx-l.js} +142 -140
  3. package/dist/index.html +2 -2
  4. package/dist-server/server/index.js +6 -0
  5. package/dist-server/server/index.js.map +1 -1
  6. package/dist-server/server/modules/orchestration/index.js +1 -0
  7. package/dist-server/server/modules/orchestration/index.js.map +1 -1
  8. package/dist-server/server/modules/orchestration/workflows/approval-queue.js +72 -0
  9. package/dist-server/server/modules/orchestration/workflows/approval-queue.js.map +1 -0
  10. package/dist-server/server/modules/orchestration/workflows/workflow-runner.js +25 -0
  11. package/dist-server/server/modules/orchestration/workflows/workflow-runner.js.map +1 -1
  12. package/dist-server/server/modules/orchestration/workflows/workflow.routes.js +87 -0
  13. package/dist-server/server/modules/orchestration/workflows/workflow.routes.js.map +1 -1
  14. package/dist-server/server/routes/production-agent-loop.js +67 -0
  15. package/dist-server/server/routes/production-agent-loop.js.map +1 -0
  16. package/dist-server/server/routes/public-api.js +7 -1
  17. package/dist-server/server/routes/public-api.js.map +1 -1
  18. package/dist-server/server/routes/remote.js +18 -0
  19. package/dist-server/server/routes/remote.js.map +1 -1
  20. package/dist-server/server/routes/webhooks.js +53 -0
  21. package/dist-server/server/routes/webhooks.js.map +1 -0
  22. package/dist-server/server/services/control-room.js +89 -0
  23. package/dist-server/server/services/control-room.js.map +1 -0
  24. package/dist-server/server/services/production-agent-loop.js +233 -0
  25. package/dist-server/server/services/production-agent-loop.js.map +1 -0
  26. package/dist-server/server/services/public-api-manifest.js +96 -0
  27. package/dist-server/server/services/public-api-manifest.js.map +1 -1
  28. package/dist-server/server/services/telegram/control-center.js +110 -0
  29. package/dist-server/server/services/telegram/control-center.js.map +1 -1
  30. package/dist-server/server/services/telegram/translations.js +24 -2
  31. package/dist-server/server/services/telegram/translations.js.map +1 -1
  32. package/dist-server/server/services/webhooks.js +198 -0
  33. package/dist-server/server/services/webhooks.js.map +1 -0
  34. package/package.json +1 -1
  35. package/scripts/smoke/v143-remote-control.mjs +76 -0
  36. package/scripts/smoke/v144-production-loop.mjs +47 -0
  37. package/server/index.js +8 -0
  38. package/server/modules/orchestration/index.ts +4 -0
  39. package/server/modules/orchestration/workflows/approval-queue.ts +106 -0
  40. package/server/modules/orchestration/workflows/workflow-runner.ts +25 -0
  41. package/server/modules/orchestration/workflows/workflow.routes.ts +95 -0
  42. package/server/routes/production-agent-loop.js +90 -0
  43. package/server/routes/public-api.js +14 -1
  44. package/server/routes/remote.js +22 -0
  45. package/server/routes/webhooks.js +63 -0
  46. package/server/services/control-room.js +102 -0
  47. package/server/services/production-agent-loop.js +248 -0
  48. package/server/services/public-api-manifest.js +98 -0
  49. package/server/services/telegram/control-center.js +113 -0
  50. package/server/services/telegram/translations.js +24 -2
  51. package/server/services/webhooks.js +216 -0
  52. package/dist/assets/index-CHa1760s.css +0 -32
@@ -14,6 +14,11 @@ import {
14
14
  import { workflowStore } from '@/modules/orchestration/workflows/workflow-store.js';
15
15
  import { buildWorkflowTrace } from '@/modules/orchestration/workflows/workflow-trace.js';
16
16
  import { findPixcodeAppRoot } from '@/modules/orchestration/workflows/workspace-target.js';
17
+ import {
18
+ listPendingApprovals,
19
+ resolvePermissionApproval,
20
+ type ApprovalDecisionSource,
21
+ } from '@/modules/orchestration/workflows/approval-queue.js';
17
22
  import {
18
23
  DEFAULT_PERMISSION_POLICY,
19
24
  PERMISSION_CAPABILITIES,
@@ -22,6 +27,7 @@ import {
22
27
  evaluatePermissionRequest,
23
28
  normalizePermissionPolicy,
24
29
  } from '@/modules/orchestration/security/permission-policy.js';
30
+ import { dispatchWebhookEvent } from '@/services/webhooks.js';
25
31
 
26
32
  const TERMINAL_RUN_STATES = new Set(['completed', 'failed', 'canceled']);
27
33
 
@@ -254,6 +260,55 @@ export function createWorkflowRouter(): Router {
254
260
  });
255
261
  });
256
262
 
263
+ router.get('/workflows/approvals', (req, res) => {
264
+ res.json({
265
+ pendingApprovals: listPendingApprovals({
266
+ projectId: readOptionalString(req.query.projectId),
267
+ includeResolved: readBooleanFlag(req.query.includeResolved),
268
+ }),
269
+ });
270
+ });
271
+
272
+ router.post('/workflows/approvals/:approvalId', (req, res) => {
273
+ const allow = req.body?.allow === true;
274
+ const deny = req.body?.allow === false;
275
+ if (!allow && !deny) {
276
+ res.status(400).json({
277
+ error: {
278
+ code: 'PERMISSION_DECISION_REQUIRED',
279
+ message: 'Approval queue decisions require allow=true or allow=false.',
280
+ },
281
+ });
282
+ return;
283
+ }
284
+
285
+ const source = ['ui', 'telegram', 'api'].includes(req.body?.source)
286
+ ? req.body.source as ApprovalDecisionSource
287
+ : 'api';
288
+ const result = resolvePermissionApproval({
289
+ approvalId: req.params.approvalId,
290
+ allow,
291
+ source,
292
+ resolvedBy: readRequestUserId(req),
293
+ message: readOptionalString(req.body?.message),
294
+ });
295
+ if (!result) {
296
+ res.status(404).json({ error: { code: 'APPROVAL_NOT_FOUND', message: req.params.approvalId } });
297
+ return;
298
+ }
299
+
300
+ dispatchWebhookEvent({
301
+ type: 'approval.resolved',
302
+ payload: {
303
+ approvalId: req.params.approvalId,
304
+ allow,
305
+ source,
306
+ runId: result.runId,
307
+ },
308
+ });
309
+ res.json(result);
310
+ });
311
+
257
312
  router.post('/workflows/runs/:runId/permission-approvals/:requestId', (req, res) => {
258
313
  const run = workflowStore.getRun(req.params.runId);
259
314
  if (!run) {
@@ -285,6 +340,15 @@ export function createWorkflowRouter(): Router {
285
340
  }
286
341
 
287
342
  workflowStore.setRun(run);
343
+ dispatchWebhookEvent({
344
+ type: 'approval.resolved',
345
+ payload: {
346
+ approvalId: req.params.requestId,
347
+ allow,
348
+ source: 'ui',
349
+ runId: run.id,
350
+ },
351
+ });
288
352
  res.json({
289
353
  runId: run.id,
290
354
  pendingApprovals: readRunArray(run, 'pendingPermissionApprovals')
@@ -359,6 +423,14 @@ export function createWorkflowRouter(): Router {
359
423
  },
360
424
  },
361
425
  );
426
+ dispatchWebhookEvent({
427
+ type: 'run.started',
428
+ payload: {
429
+ runId: replayRun.id,
430
+ workflowId: replayRun.workflowId,
431
+ replayOf: run.id,
432
+ },
433
+ });
362
434
  res.status(202).json({
363
435
  run: replayRun,
364
436
  replayPlan,
@@ -424,6 +496,13 @@ export function createWorkflowRouter(): Router {
424
496
  res.status(404).json({ error: { code: 'RUN_NOT_FOUND', message: req.params.runId } });
425
497
  return;
426
498
  }
499
+ dispatchWebhookEvent({
500
+ type: 'run.canceled',
501
+ payload: {
502
+ runId: run.id,
503
+ workflowId: run.workflowId,
504
+ },
505
+ });
427
506
  res.json(run);
428
507
  });
429
508
 
@@ -443,6 +522,14 @@ export function createWorkflowRouter(): Router {
443
522
  workflowName: workflow.name,
444
523
  },
445
524
  );
525
+ dispatchWebhookEvent({
526
+ type: 'run.started',
527
+ payload: {
528
+ runId: run.id,
529
+ workflowId: run.workflowId,
530
+ workflowName: workflow.name,
531
+ },
532
+ });
446
533
  res.status(202).json(run);
447
534
  } catch (error) {
448
535
  res.status(400).json({
@@ -476,6 +563,14 @@ export function createWorkflowRouter(): Router {
476
563
  workflowName: workflow.name,
477
564
  },
478
565
  );
566
+ dispatchWebhookEvent({
567
+ type: 'run.started',
568
+ payload: {
569
+ runId: run.id,
570
+ workflowId: run.workflowId,
571
+ templateId: template.id,
572
+ },
573
+ });
479
574
  res.status(202).json(run);
480
575
  } catch (error) {
481
576
  res.status(400).json({
@@ -0,0 +1,90 @@
1
+ import express from 'express';
2
+
3
+ import {
4
+ createIssueToPrRun,
5
+ createReviewQueueItem,
6
+ createWorkspaceCheckpoint,
7
+ evaluateDesktopReleaseAssetPolicy,
8
+ getProductionAgentLoopState,
9
+ parseCiRepairSignals,
10
+ scheduleBackgroundAgentJob,
11
+ updateReviewQueueItem,
12
+ } from '../services/production-agent-loop.js';
13
+
14
+ const router = express.Router();
15
+
16
+ function userId(req) {
17
+ return req.user?.id ?? req.user?.userId ?? null;
18
+ }
19
+
20
+ router.get('/', (_req, res) => {
21
+ res.json({ success: true, state: getProductionAgentLoopState() });
22
+ });
23
+
24
+ router.post('/github/issue-to-pr', (req, res) => {
25
+ try {
26
+ const run = createIssueToPrRun(req.body || {}, userId(req));
27
+ res.status(202).json({ success: true, run });
28
+ } catch (error) {
29
+ res.status(400).json({ success: false, error: error.message });
30
+ }
31
+ });
32
+
33
+ router.post('/ci/repair-plan', (req, res) => {
34
+ res.json({
35
+ success: true,
36
+ repairPlan: parseCiRepairSignals(req.body?.log || req.body?.output || ''),
37
+ });
38
+ });
39
+
40
+ router.get('/review-queue', (_req, res) => {
41
+ res.json({ success: true, reviewQueue: getProductionAgentLoopState().reviewQueue });
42
+ });
43
+
44
+ router.post('/review-queue', (req, res) => {
45
+ const item = createReviewQueueItem(req.body || {}, userId(req));
46
+ res.status(201).json({ success: true, item });
47
+ });
48
+
49
+ router.patch('/review-queue/:id', (req, res) => {
50
+ const item = updateReviewQueueItem(req.params.id, req.body || {});
51
+ if (!item) {
52
+ res.status(404).json({ success: false, error: 'Review queue item not found.' });
53
+ return;
54
+ }
55
+ res.json({ success: true, item });
56
+ });
57
+
58
+ router.get('/scheduler/jobs', (_req, res) => {
59
+ res.json({ success: true, jobs: getProductionAgentLoopState().schedulerJobs });
60
+ });
61
+
62
+ router.post('/scheduler/jobs', (req, res) => {
63
+ const job = scheduleBackgroundAgentJob(req.body || {}, userId(req));
64
+ res.status(201).json({ success: true, job });
65
+ });
66
+
67
+ router.get('/snapshots', (_req, res) => {
68
+ res.json({ success: true, checkpoints: getProductionAgentLoopState().checkpoints });
69
+ });
70
+
71
+ router.post('/snapshots', (req, res) => {
72
+ const checkpoint = createWorkspaceCheckpoint(req.body || {}, userId(req));
73
+ res.status(201).json({ success: true, checkpoint });
74
+ });
75
+
76
+ router.post('/desktop-release/assets-policy', (req, res) => {
77
+ res.json({
78
+ success: true,
79
+ policy: evaluateDesktopReleaseAssetPolicy(req.body?.assets || req.body?.assetNames || []),
80
+ });
81
+ });
82
+
83
+ router.get('/desktop-release/assets-policy', (_req, res) => {
84
+ res.json({
85
+ success: true,
86
+ policy: evaluateDesktopReleaseAssetPolicy([]),
87
+ });
88
+ });
89
+
90
+ export default router;
@@ -1,6 +1,11 @@
1
1
  import express from 'express';
2
2
 
3
- import { buildOpenApiFragment, buildPublicApiManifest } from '../services/public-api-manifest.js';
3
+ import {
4
+ buildCurlCookbook,
5
+ buildOpenApiFragment,
6
+ buildPublicApiManifest,
7
+ buildTypeScriptSdkStarter,
8
+ } from '../services/public-api-manifest.js';
4
9
 
5
10
  const router = express.Router();
6
11
 
@@ -18,4 +23,12 @@ router.get('/openapi', (req, res) => {
18
23
  res.json(buildOpenApiFragment({ baseUrl: requestBaseUrl(req) }));
19
24
  });
20
25
 
26
+ router.get('/sdk/typescript', (req, res) => {
27
+ res.type('text/typescript').send(buildTypeScriptSdkStarter({ baseUrl: requestBaseUrl(req) }));
28
+ });
29
+
30
+ router.get('/cookbook', (req, res) => {
31
+ res.json(buildCurlCookbook({ baseUrl: requestBaseUrl(req) }));
32
+ });
33
+
21
34
  export default router;
@@ -5,6 +5,10 @@ import {
5
5
  getPublicRemoteConnectionConfig,
6
6
  saveRemoteConnectionConfig,
7
7
  } from '../services/remote-connection.js';
8
+ import {
9
+ buildControlRoomSnapshot,
10
+ buildMobileConsoleLayout,
11
+ } from '../services/control-room.js';
8
12
 
9
13
  const router = express.Router();
10
14
 
@@ -30,4 +34,22 @@ router.post('/check', async (req, res) => {
30
34
  }
31
35
  });
32
36
 
37
+ router.get('/control-room', async (_req, res) => {
38
+ try {
39
+ res.json({
40
+ success: true,
41
+ controlRoom: await buildControlRoomSnapshot(),
42
+ });
43
+ } catch (error) {
44
+ res.status(500).json({ success: false, error: error.message });
45
+ }
46
+ });
47
+
48
+ router.get('/console-layout', (_req, res) => {
49
+ res.json({
50
+ success: true,
51
+ layout: buildMobileConsoleLayout(),
52
+ });
53
+ });
54
+
33
55
  export default router;
@@ -0,0 +1,63 @@
1
+ import express from 'express';
2
+
3
+ import {
4
+ PIXCODE_WEBHOOK_EVENT_TYPES,
5
+ deleteWebhook,
6
+ deliverWebhookEvent,
7
+ listWebhooks,
8
+ upsertWebhook,
9
+ } from '../services/webhooks.js';
10
+
11
+ const router = express.Router();
12
+
13
+ router.get('/', (_req, res) => {
14
+ res.json({
15
+ success: true,
16
+ eventTypes: PIXCODE_WEBHOOK_EVENT_TYPES,
17
+ webhooks: listWebhooks(),
18
+ });
19
+ });
20
+
21
+ router.post('/', (req, res) => {
22
+ try {
23
+ const webhook = upsertWebhook(req.body || {});
24
+ res.status(201).json({ success: true, webhook });
25
+ } catch (error) {
26
+ res.status(400).json({ success: false, error: error.message });
27
+ }
28
+ });
29
+
30
+ router.patch('/:id', (req, res) => {
31
+ try {
32
+ const webhook = upsertWebhook({ ...(req.body || {}), id: req.params.id });
33
+ res.json({ success: true, webhook });
34
+ } catch (error) {
35
+ res.status(400).json({ success: false, error: error.message });
36
+ }
37
+ });
38
+
39
+ router.delete('/:id', (req, res) => {
40
+ if (!deleteWebhook(req.params.id)) {
41
+ res.status(404).json({ success: false, error: 'Webhook not found.' });
42
+ return;
43
+ }
44
+ res.json({ success: true });
45
+ });
46
+
47
+ router.post('/test', async (req, res) => {
48
+ try {
49
+ const result = await deliverWebhookEvent({
50
+ type: req.body?.type || 'run.completed',
51
+ payload: {
52
+ test: true,
53
+ message: 'Pixcode webhook test delivery',
54
+ sentBy: req.user?.id ?? null,
55
+ },
56
+ });
57
+ res.json({ success: true, result });
58
+ } catch (error) {
59
+ res.status(500).json({ success: false, error: error.message });
60
+ }
61
+ });
62
+
63
+ export default router;
@@ -0,0 +1,102 @@
1
+ import { listPendingApprovals, workflowStore } from '../modules/orchestration/index.js';
2
+ import { getProjects } from '../projects.js';
3
+
4
+ import { listWebhooks } from './webhooks.js';
5
+
6
+ const TERMINAL_RUN_STATES = new Set(['completed', 'failed', 'canceled']);
7
+
8
+ function projectPath(project) {
9
+ return project.fullPath || project.path || '';
10
+ }
11
+
12
+ function runBelongsToProject(run, project) {
13
+ const projectId = run.metadata?.projectId;
14
+ const path = run.metadata?.projectPath;
15
+ return projectId === project.name || path === projectPath(project);
16
+ }
17
+
18
+ function countSessions(project) {
19
+ return [
20
+ project.sessions,
21
+ project.codexSessions,
22
+ project.cursorSessions,
23
+ project.geminiSessions,
24
+ project.qwenSessions,
25
+ project.opencodeSessions,
26
+ ].reduce((total, sessions) => total + (Array.isArray(sessions) ? sessions.length : 0), 0);
27
+ }
28
+
29
+ function summarizeProject(project, runs, approvals) {
30
+ const projectRuns = runs.filter((run) => runBelongsToProject(run, project));
31
+ const runningRuns = projectRuns.filter((run) => !TERMINAL_RUN_STATES.has(run.status));
32
+ const failedRuns = projectRuns.filter((run) => run.status === 'failed');
33
+ const projectApprovals = approvals.filter((approval) => (
34
+ approval.runId && projectRuns.some((run) => run.id === approval.runId)
35
+ ));
36
+
37
+ return {
38
+ id: project.name,
39
+ name: project.displayName || project.name,
40
+ path: projectPath(project),
41
+ sessionCount: countSessions(project),
42
+ activeRunCount: runningRuns.length,
43
+ failedRunCount: failedRuns.length,
44
+ pendingApprovalCount: projectApprovals.length,
45
+ latestRuns: projectRuns.slice(0, 4).map((run) => ({
46
+ id: run.id,
47
+ workflowId: run.workflowId,
48
+ status: run.status,
49
+ startedAt: run.startedAt,
50
+ finishedAt: run.finishedAt,
51
+ })),
52
+ };
53
+ }
54
+
55
+ export async function buildControlRoomSnapshot({ maxProjects = 4 } = {}) {
56
+ const projects = await getProjects();
57
+ const runs = workflowStore.listRuns();
58
+ const pendingApprovals = listPendingApprovals();
59
+ const webhooks = listWebhooks();
60
+ const projectCards = projects
61
+ .map((project) => summarizeProject(project, runs, pendingApprovals))
62
+ .sort((a, b) => (
63
+ b.pendingApprovalCount - a.pendingApprovalCount ||
64
+ b.activeRunCount - a.activeRunCount ||
65
+ b.failedRunCount - a.failedRunCount ||
66
+ a.name.localeCompare(b.name)
67
+ ))
68
+ .slice(0, maxProjects);
69
+
70
+ return {
71
+ protocol: 'pixcode.control-room.v1',
72
+ generatedAt: new Date().toISOString(),
73
+ maxProjects,
74
+ mobileFirst: true,
75
+ totals: {
76
+ projects: projects.length,
77
+ activeRuns: runs.filter((run) => !TERMINAL_RUN_STATES.has(run.status)).length,
78
+ failedRuns: runs.filter((run) => run.status === 'failed').length,
79
+ pendingApprovals: pendingApprovals.length,
80
+ webhooks: webhooks.length,
81
+ enabledWebhooks: webhooks.filter((webhook) => webhook.enabled).length,
82
+ },
83
+ projects: projectCards,
84
+ approvals: pendingApprovals.slice(0, 20),
85
+ webhooks,
86
+ };
87
+ }
88
+
89
+ export function buildMobileConsoleLayout() {
90
+ return {
91
+ protocol: 'pixcode.remote-console-layout.v1',
92
+ mobileFirst: true,
93
+ sections: [
94
+ { id: 'projects', title: 'Projects', priority: 1 },
95
+ { id: 'approvals', title: 'Approval queue', priority: 2 },
96
+ { id: 'runs', title: 'Runs', priority: 3 },
97
+ { id: 'webhooks', title: 'Webhooks', priority: 4 },
98
+ { id: 'api', title: 'API SDK', priority: 5 },
99
+ ],
100
+ maxVisibleProjects: 4,
101
+ };
102
+ }