@pixelbyte-software/pixcode 1.47.4 → 1.48.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/README.md +16 -30
- package/README.tr.md +1 -1
- package/dist/api-automation.html +1 -1
- package/dist/assets/index-DlAWSDTA.js +818 -0
- package/dist/assets/index-g9NMlzYt.css +32 -0
- package/dist/assets/{vendor-codemirror-CzSp4P1a.js → vendor-codemirror-CIYNS698.js} +8 -8
- package/dist/docs.html +10 -10
- package/dist/features.html +3 -3
- package/dist/index.html +3 -3
- package/dist/landing.html +21 -23
- package/dist/llms-full.txt +4 -8
- package/dist/llms.txt +5 -6
- package/dist/openapi.yaml +7 -7
- package/dist-server/server/index.js +4 -6
- package/dist-server/server/index.js.map +1 -1
- package/dist-server/server/modules/orchestration/a2a/adapters/claude-code.adapter.js +1 -1
- package/dist-server/server/modules/orchestration/a2a/adapters/claude-code.adapter.js.map +1 -1
- package/dist-server/server/modules/orchestration/a2a/adapters/codex.adapter.js +1 -1
- package/dist-server/server/modules/orchestration/a2a/adapters/codex.adapter.js.map +1 -1
- package/dist-server/server/modules/orchestration/a2a/adapters/cursor.adapter.js +1 -1
- package/dist-server/server/modules/orchestration/a2a/adapters/cursor.adapter.js.map +1 -1
- package/dist-server/server/modules/orchestration/a2a/adapters/gemini.adapter.js +1 -1
- package/dist-server/server/modules/orchestration/a2a/adapters/gemini.adapter.js.map +1 -1
- package/dist-server/server/modules/orchestration/a2a/adapters/opencode.adapter.js +1 -1
- package/dist-server/server/modules/orchestration/a2a/adapters/opencode.adapter.js.map +1 -1
- package/dist-server/server/modules/orchestration/a2a/adapters/qwen.adapter.js +1 -1
- package/dist-server/server/modules/orchestration/a2a/adapters/qwen.adapter.js.map +1 -1
- package/dist-server/server/modules/orchestration/a2a/agent-card.js +4 -4
- package/dist-server/server/modules/orchestration/a2a/agent-card.js.map +1 -1
- package/dist-server/server/modules/orchestration/a2a/routes.js +6 -2
- package/dist-server/server/modules/orchestration/a2a/routes.js.map +1 -1
- package/dist-server/server/modules/orchestration/hermes/hermes.routes.js +62 -0
- package/dist-server/server/modules/orchestration/hermes/hermes.routes.js.map +1 -0
- package/dist-server/server/modules/orchestration/index.js +2 -1
- package/dist-server/server/modules/orchestration/index.js.map +1 -1
- package/dist-server/server/modules/orchestration/tasks/orchestration-task-store.js +2 -5
- package/dist-server/server/modules/orchestration/tasks/orchestration-task-store.js.map +1 -1
- package/dist-server/server/modules/orchestration/tasks/orchestration-task.routes.js +1 -20
- package/dist-server/server/modules/orchestration/tasks/orchestration-task.routes.js.map +1 -1
- package/dist-server/server/modules/orchestration/tasks/orchestration-task.service.js +4 -37
- package/dist-server/server/modules/orchestration/tasks/orchestration-task.service.js.map +1 -1
- package/dist-server/server/modules/orchestration/tasks/task-run-graph.js +4 -62
- package/dist-server/server/modules/orchestration/tasks/task-run-graph.js.map +1 -1
- package/dist-server/server/modules/orchestration/workflows/workflow-runner.js +14 -14
- package/dist-server/server/modules/orchestration/workflows/workflow-runner.js.map +1 -1
- package/dist-server/server/modules/orchestration/workflows/workflow-trace.js +2 -2
- package/dist-server/server/modules/orchestration/workflows/workflow-trace.js.map +1 -1
- package/dist-server/server/modules/providers/list/gemini/gemini-auth.provider.js +9 -1
- package/dist-server/server/modules/providers/list/gemini/gemini-auth.provider.js.map +1 -1
- package/dist-server/server/projects.js +0 -160
- package/dist-server/server/projects.js.map +1 -1
- package/dist-server/server/routes/mcp-utils.js +0 -18
- package/dist-server/server/routes/mcp-utils.js.map +1 -1
- package/dist-server/server/services/public-api-manifest.js +0 -5
- package/dist-server/server/services/public-api-manifest.js.map +1 -1
- package/dist-server/server/services/telegram/control-center.js +2 -144
- package/dist-server/server/services/telegram/control-center.js.map +1 -1
- package/dist-server/server/services/telegram/translations.js +2 -14
- package/dist-server/server/services/telegram/translations.js.map +1 -1
- package/package.json +1 -2
- package/scripts/smoke/default-landing-routing.mjs +7 -16
- package/scripts/smoke/{a2a-roundtrip.mjs → hermes-roundtrip.mjs} +23 -23
- package/scripts/smoke/mac-desktop-runtime.mjs +1 -7
- package/scripts/smoke/orchestration-live-run.mjs +1 -1
- package/scripts/smoke/orchestration-model-sync.mjs +2 -2
- package/scripts/smoke/orchestration-user-facing-output.mjs +2 -2
- package/scripts/smoke/pixcode-workbench-1-48.mjs +55 -0
- package/scripts/smoke/provider-selection-status.mjs +52 -0
- package/scripts/smoke/taskmaster-config.mjs +21 -56
- package/scripts/smoke/taskmaster-execution-telegram.mjs +1 -50
- package/scripts/smoke/taskmaster-onboarding.mjs +1 -50
- package/scripts/smoke/taskmaster-run-graph.mjs +1 -53
- package/scripts/smoke/vscode-workbench-layout.mjs +25 -62
- package/scripts/smoke/vscode-workbench-polish.mjs +19 -5
- package/server/index.js +5 -7
- package/server/modules/orchestration/a2a/adapters/claude-code.adapter.ts +1 -1
- package/server/modules/orchestration/a2a/adapters/codex.adapter.ts +1 -1
- package/server/modules/orchestration/a2a/adapters/cursor.adapter.ts +1 -1
- package/server/modules/orchestration/a2a/adapters/gemini.adapter.ts +1 -1
- package/server/modules/orchestration/a2a/adapters/opencode.adapter.ts +1 -1
- package/server/modules/orchestration/a2a/adapters/qwen.adapter.ts +1 -1
- package/server/modules/orchestration/a2a/agent-card.ts +4 -4
- package/server/modules/orchestration/a2a/routes.ts +7 -2
- package/server/modules/orchestration/hermes/hermes.routes.ts +69 -0
- package/server/modules/orchestration/index.ts +2 -1
- package/server/modules/orchestration/tasks/orchestration-task-store.ts +2 -6
- package/server/modules/orchestration/tasks/orchestration-task.routes.ts +1 -20
- package/server/modules/orchestration/tasks/orchestration-task.service.ts +4 -41
- package/server/modules/orchestration/tasks/orchestration-task.types.ts +2 -4
- package/server/modules/orchestration/tasks/task-run-graph.ts +6 -70
- package/server/modules/orchestration/workflows/workflow-runner.ts +14 -14
- package/server/modules/orchestration/workflows/workflow-trace.ts +2 -2
- package/server/modules/orchestration/workflows/workflow.types.ts +1 -1
- package/server/modules/providers/list/gemini/gemini-auth.provider.ts +10 -1
- package/server/projects.js +0 -170
- package/server/routes/mcp-utils.js +0 -19
- package/server/services/public-api-manifest.js +0 -5
- package/server/services/telegram/control-center.js +2 -153
- package/server/services/telegram/translations.js +2 -14
- package/dist/assets/index-BBdWwJi6.css +0 -32
- package/dist/assets/index-C7zmMv0b.js +0 -889
- package/dist-server/server/routes/taskmaster.js +0 -1793
- package/dist-server/server/routes/taskmaster.js.map +0 -1
- package/dist-server/server/services/taskmaster-config.js +0 -128
- package/dist-server/server/services/taskmaster-config.js.map +0 -1
- package/dist-server/server/utils/mcp-detector.js +0 -134
- package/dist-server/server/utils/mcp-detector.js.map +0 -1
- package/dist-server/server/utils/taskmaster-websocket.js +0 -118
- package/dist-server/server/utils/taskmaster-websocket.js.map +0 -1
- package/server/routes/taskmaster.js +0 -1918
- package/server/services/taskmaster-config.js +0 -146
- package/server/utils/mcp-detector.js +0 -147
- package/server/utils/taskmaster-websocket.js +0 -129
|
@@ -41,6 +41,18 @@ assert.match(
|
|
|
41
41
|
'Projects activity should use a dedicated project-directory panel.',
|
|
42
42
|
);
|
|
43
43
|
|
|
44
|
+
assert.match(
|
|
45
|
+
workbench,
|
|
46
|
+
/function WorkbenchProjectLanding/,
|
|
47
|
+
'Workbench center should show a project landing page instead of a blank editor when no project is selected.',
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
assert.match(
|
|
51
|
+
workbench,
|
|
52
|
+
/function WorkbenchCliPanel/,
|
|
53
|
+
'Right workbench pane should use a terminal-only CLI panel.',
|
|
54
|
+
);
|
|
55
|
+
|
|
44
56
|
assert.doesNotMatch(
|
|
45
57
|
workbench,
|
|
46
58
|
/return <Sidebar \{\.\.\.sidebarProps\} isMobile=\{false\} \/>/,
|
|
@@ -59,6 +71,12 @@ assert.match(
|
|
|
59
71
|
'Projects panel should show file counts.',
|
|
60
72
|
);
|
|
61
73
|
|
|
74
|
+
assert.match(
|
|
75
|
+
workbench,
|
|
76
|
+
/Work in this folder/,
|
|
77
|
+
'Project cards should make the folder binding explicit.',
|
|
78
|
+
);
|
|
79
|
+
|
|
62
80
|
assert.match(projectType, /fileCount\?: number/, 'Project type should expose optional fileCount metadata.');
|
|
63
81
|
assert.match(projectsServer, /async function countProjectFiles/, 'Backend should count project files for the workbench project list.');
|
|
64
82
|
|
|
@@ -74,11 +92,7 @@ assert.doesNotMatch(
|
|
|
74
92
|
'Workbench center panel should be allowed to shrink with narrow three-pane layouts.',
|
|
75
93
|
);
|
|
76
94
|
|
|
77
|
-
assert.
|
|
78
|
-
workbench,
|
|
79
|
-
/compactComposer/,
|
|
80
|
-
'Workbench should request compact composer behavior in the right CLI pane.',
|
|
81
|
-
);
|
|
95
|
+
assert.doesNotMatch(workbench, /<ChatInterface/, 'Workbench should not embed the chat composer in the right CLI pane.');
|
|
82
96
|
|
|
83
97
|
assert.match(
|
|
84
98
|
workbench,
|
package/server/index.js
CHANGED
|
@@ -58,7 +58,6 @@ import sessionManager from './sessionManager.js';
|
|
|
58
58
|
import gitRoutes from './routes/git.js';
|
|
59
59
|
import authRoutes from './routes/auth.js';
|
|
60
60
|
import cursorRoutes from './routes/cursor.js';
|
|
61
|
-
import taskmasterRoutes from './routes/taskmaster.js';
|
|
62
61
|
import mcpUtilsRoutes from './routes/mcp-utils.js';
|
|
63
62
|
import commandsRoutes from './routes/commands.js';
|
|
64
63
|
import settingsRoutes from './routes/settings.js';
|
|
@@ -84,7 +83,7 @@ import platformizationRoutes from './routes/platformization.js';
|
|
|
84
83
|
import liveViewRoutes, { createLiveViewPublicRouter } from './routes/live-view.js';
|
|
85
84
|
import providerRoutes from './modules/providers/provider.routes.js';
|
|
86
85
|
import {
|
|
87
|
-
|
|
86
|
+
createHermesTaskRouter,
|
|
88
87
|
adapterRegistry,
|
|
89
88
|
ClaudeCodeA2AAdapter,
|
|
90
89
|
CodexA2AAdapter,
|
|
@@ -94,6 +93,7 @@ import {
|
|
|
94
93
|
QwenA2AAdapter,
|
|
95
94
|
createPreviewProxyRouter,
|
|
96
95
|
createOrchestrationTaskRouter,
|
|
96
|
+
createHermesRouter,
|
|
97
97
|
createWorkflowRouter,
|
|
98
98
|
} from './modules/orchestration/index.js';
|
|
99
99
|
import networkRoutes from './routes/network.js';
|
|
@@ -370,9 +370,6 @@ app.use('/api/git', authenticateToken, gitRoutes);
|
|
|
370
370
|
// Cursor API Routes (protected)
|
|
371
371
|
app.use('/api/cursor', authenticateToken, cursorRoutes);
|
|
372
372
|
|
|
373
|
-
// TaskMaster API Routes (protected)
|
|
374
|
-
app.use('/api/taskmaster', authenticateToken, taskmasterRoutes);
|
|
375
|
-
|
|
376
373
|
// MCP utilities
|
|
377
374
|
app.use('/api/mcp-utils', authenticateToken, mcpUtilsRoutes);
|
|
378
375
|
|
|
@@ -424,16 +421,17 @@ app.use('/api/live-view', authenticateToken, liveViewRoutes);
|
|
|
424
421
|
// Unified provider MCP routes (protected)
|
|
425
422
|
app.use('/api/providers', authenticateToken, providerRoutes);
|
|
426
423
|
|
|
427
|
-
//
|
|
424
|
+
// Hermes internal task router has its own localhost/auth middleware; do not wrap with authenticateToken.
|
|
428
425
|
adapterRegistry.register(new ClaudeCodeA2AAdapter());
|
|
429
426
|
adapterRegistry.register(new CodexA2AAdapter());
|
|
430
427
|
adapterRegistry.register(new CursorA2AAdapter());
|
|
431
428
|
adapterRegistry.register(new GeminiA2AAdapter());
|
|
432
429
|
adapterRegistry.register(new QwenA2AAdapter());
|
|
433
430
|
adapterRegistry.register(new OpenCodeA2AAdapter());
|
|
434
|
-
app.use('/
|
|
431
|
+
app.use('/hermes', createHermesTaskRouter());
|
|
435
432
|
app.use('/preview', authenticateToken, createPreviewProxyRouter());
|
|
436
433
|
app.use('/api/orchestration', authenticateToken, createOrchestrationTaskRouter());
|
|
434
|
+
app.use('/api/orchestration/hermes', authenticateToken, createHermesRouter());
|
|
437
435
|
app.use('/api/orchestration', authenticateToken, createWorkflowRouter());
|
|
438
436
|
app.use('/live', createLiveViewPublicRouter());
|
|
439
437
|
|
|
@@ -49,7 +49,7 @@ export class ClaudeCodeA2AAdapter extends AbstractA2AAdapter {
|
|
|
49
49
|
readonly agentCard: AgentCard = {
|
|
50
50
|
name: 'pixcode-claude-code',
|
|
51
51
|
description: 'Anthropic Claude Code, accessed via Pixcode',
|
|
52
|
-
url: '/
|
|
52
|
+
url: '/hermes/agents/claude-code',
|
|
53
53
|
version: '1.0.0',
|
|
54
54
|
capabilities: ['streaming', 'fileEdit', 'commandExec', 'mcp'],
|
|
55
55
|
skills: [
|
|
@@ -40,7 +40,7 @@ export class CodexA2AAdapter extends AbstractA2AAdapter {
|
|
|
40
40
|
readonly agentCard: AgentCard = {
|
|
41
41
|
name: 'pixcode-codex',
|
|
42
42
|
description: 'OpenAI Codex, accessed via Pixcode',
|
|
43
|
-
url: '/
|
|
43
|
+
url: '/hermes/agents/codex',
|
|
44
44
|
version: '1.0.0',
|
|
45
45
|
capabilities: ['streaming', 'fileEdit', 'commandExec', 'webSearch', 'mcp'],
|
|
46
46
|
skills: [
|
|
@@ -40,7 +40,7 @@ export class CursorA2AAdapter extends AbstractA2AAdapter {
|
|
|
40
40
|
readonly agentCard: AgentCard = {
|
|
41
41
|
name: 'pixcode-cursor',
|
|
42
42
|
description: 'Cursor CLI, accessed via Pixcode',
|
|
43
|
-
url: '/
|
|
43
|
+
url: '/hermes/agents/cursor',
|
|
44
44
|
version: '1.0.0',
|
|
45
45
|
capabilities: ['streaming', 'fileEdit', 'commandExec'],
|
|
46
46
|
skills: [
|
|
@@ -40,7 +40,7 @@ export class GeminiA2AAdapter extends AbstractA2AAdapter {
|
|
|
40
40
|
readonly agentCard: AgentCard = {
|
|
41
41
|
name: 'pixcode-gemini',
|
|
42
42
|
description: 'Google Gemini CLI, accessed via Pixcode',
|
|
43
|
-
url: '/
|
|
43
|
+
url: '/hermes/agents/gemini',
|
|
44
44
|
version: '1.0.0',
|
|
45
45
|
capabilities: ['streaming', 'fileEdit', 'commandExec', 'mcp'],
|
|
46
46
|
skills: [
|
|
@@ -40,7 +40,7 @@ export class OpenCodeA2AAdapter extends AbstractA2AAdapter {
|
|
|
40
40
|
readonly agentCard: AgentCard = {
|
|
41
41
|
name: 'pixcode-opencode',
|
|
42
42
|
description: 'OpenCode CLI, accessed via Pixcode',
|
|
43
|
-
url: '/
|
|
43
|
+
url: '/hermes/agents/opencode',
|
|
44
44
|
version: '1.0.0',
|
|
45
45
|
capabilities: ['streaming', 'fileEdit', 'commandExec', 'multiProvider'],
|
|
46
46
|
skills: [
|
|
@@ -40,7 +40,7 @@ export class QwenA2AAdapter extends AbstractA2AAdapter {
|
|
|
40
40
|
readonly agentCard: AgentCard = {
|
|
41
41
|
name: 'pixcode-qwen',
|
|
42
42
|
description: 'Qwen Code CLI, accessed via Pixcode',
|
|
43
|
-
url: '/
|
|
43
|
+
url: '/hermes/agents/qwen',
|
|
44
44
|
version: '1.0.0',
|
|
45
45
|
capabilities: ['streaming', 'fileEdit', 'commandExec', 'mcp'],
|
|
46
46
|
skills: [
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// server/modules/orchestration/a2a/agent-card.ts
|
|
2
|
-
// Pixcode advertises
|
|
3
|
-
// Per-CLI adapters publish their own cards under /
|
|
2
|
+
// Pixcode advertises Hermes as one local control agent at /hermes/.well-known/agent-card.json.
|
|
3
|
+
// Per-CLI adapters publish their own cards under /hermes/agents/:id/agent-card.
|
|
4
4
|
|
|
5
5
|
import { readFileSync } from 'node:fs';
|
|
6
6
|
import { dirname, resolve } from 'node:path';
|
|
@@ -44,9 +44,9 @@ export function buildPixcodeAgentCard(baseUrl: string): AgentCard {
|
|
|
44
44
|
return {
|
|
45
45
|
name: 'pixcode',
|
|
46
46
|
description:
|
|
47
|
-
'Pixcode multi-CLI orchestration platform. Routes
|
|
47
|
+
'Pixcode Hermes multi-CLI orchestration platform. Routes Hermes tasks to ' +
|
|
48
48
|
'Claude Code, Codex, Cursor, Gemini, Qwen, or OpenCode adapters.',
|
|
49
|
-
url: `${baseUrl.replace(/\/$/, '')}/
|
|
49
|
+
url: `${baseUrl.replace(/\/$/, '')}/hermes`,
|
|
50
50
|
version: VERSION,
|
|
51
51
|
capabilities: ['streaming', 'taskRouting'],
|
|
52
52
|
skills,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// server/modules/orchestration/a2a/routes.ts
|
|
2
|
-
//
|
|
2
|
+
// Internal Hermes task router. The adapter primitives still live in this legacy
|
|
3
|
+
// folder, but the public mount is /hermes.
|
|
3
4
|
|
|
4
5
|
import crypto from 'node:crypto';
|
|
5
6
|
|
|
@@ -257,7 +258,7 @@ for (const task of taskStore.values()) {
|
|
|
257
258
|
}
|
|
258
259
|
}
|
|
259
260
|
|
|
260
|
-
|
|
261
|
+
function createA2ARouter(): Router {
|
|
261
262
|
const router: Router = express.Router();
|
|
262
263
|
|
|
263
264
|
router.use(express.json({ limit: '5mb' }));
|
|
@@ -583,3 +584,7 @@ export function createA2ARouter(): Router {
|
|
|
583
584
|
|
|
584
585
|
return router;
|
|
585
586
|
}
|
|
587
|
+
|
|
588
|
+
export function createHermesTaskRouter(): Router {
|
|
589
|
+
return createA2ARouter();
|
|
590
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import os from 'node:os';
|
|
2
|
+
|
|
3
|
+
import express, { type Router } from 'express';
|
|
4
|
+
|
|
5
|
+
import { adapterRegistry } from '@/modules/orchestration/a2a/adapter-registry.js';
|
|
6
|
+
import { a2aTaskStore as hermesTaskStore } from '@/modules/orchestration/a2a/task-store.js';
|
|
7
|
+
|
|
8
|
+
export function createHermesRouter(): Router {
|
|
9
|
+
const router = express.Router();
|
|
10
|
+
|
|
11
|
+
router.get('/status', (_req, res) => {
|
|
12
|
+
res.json({
|
|
13
|
+
id: 'hermes',
|
|
14
|
+
name: 'Hermes Agent',
|
|
15
|
+
product: 'Pixcode',
|
|
16
|
+
runtime: {
|
|
17
|
+
mode: process.env.PIXCODE_MANAGED_PLATFORM === '1' ? 'managed' : 'local',
|
|
18
|
+
host: os.hostname(),
|
|
19
|
+
node: process.version,
|
|
20
|
+
pid: process.pid,
|
|
21
|
+
},
|
|
22
|
+
capabilities: [
|
|
23
|
+
'project-aware CLI orchestration',
|
|
24
|
+
'background checks and tests',
|
|
25
|
+
'workspace-scoped terminal operations',
|
|
26
|
+
'run history and artifact review',
|
|
27
|
+
],
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
router.get('/context', (req, res) => {
|
|
32
|
+
const project = typeof req.query.project === 'string' ? req.query.project : null;
|
|
33
|
+
const cwd = typeof req.query.cwd === 'string' ? req.query.cwd : process.cwd();
|
|
34
|
+
res.json({
|
|
35
|
+
agent: 'Hermes Agent',
|
|
36
|
+
product: 'Pixcode',
|
|
37
|
+
project,
|
|
38
|
+
cwd,
|
|
39
|
+
local: true,
|
|
40
|
+
message: project
|
|
41
|
+
? `Hermes is operating inside Pixcode for ${project}.`
|
|
42
|
+
: 'Hermes is operating inside Pixcode.',
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
router.get('/agents', (_req, res) => {
|
|
47
|
+
res.json({
|
|
48
|
+
agent: 'hermes',
|
|
49
|
+
agents: adapterRegistry.agentCards().map((agent) => ({
|
|
50
|
+
...agent,
|
|
51
|
+
coordinator: 'hermes',
|
|
52
|
+
})),
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
router.get('/tasks/:id', (req, res) => {
|
|
57
|
+
const task = hermesTaskStore.get(req.params.id);
|
|
58
|
+
if (!task) {
|
|
59
|
+
res.status(404).json({ error: { code: 'HERMES_TASK_NOT_FOUND', message: req.params.id } });
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
res.json({
|
|
63
|
+
...task,
|
|
64
|
+
hermesTaskId: task.id,
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
return router;
|
|
69
|
+
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// All cross-module consumers must import from here per
|
|
4
4
|
// eslint.config.js boundaries rules.
|
|
5
5
|
|
|
6
|
-
export {
|
|
6
|
+
export { createHermesTaskRouter } from './a2a/routes.js';
|
|
7
7
|
export { adapterRegistry } from './a2a/adapter-registry.js';
|
|
8
8
|
export { ClaudeCodeA2AAdapter } from './a2a/adapters/claude-code.adapter.js';
|
|
9
9
|
export { CodexA2AAdapter } from './a2a/adapters/codex.adapter.js';
|
|
@@ -30,6 +30,7 @@ export {
|
|
|
30
30
|
} from './security/permission-policy.js';
|
|
31
31
|
export { createOrchestrationTaskRouter } from './tasks/orchestration-task.routes.js';
|
|
32
32
|
export { orchestrationTaskService } from './tasks/orchestration-task.service.js';
|
|
33
|
+
export { createHermesRouter } from './hermes/hermes.routes.js';
|
|
33
34
|
export { createWorkflowRouter } from './workflows/workflow.routes.js';
|
|
34
35
|
export {
|
|
35
36
|
listPendingApprovals,
|
|
@@ -20,12 +20,8 @@ export class OrchestrationTaskStore {
|
|
|
20
20
|
return this.store.findWhere('orchestration_tasks', (r: OrchestrationTask) => r.id === id) ?? undefined;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
return this.store.findWhere('orchestration_tasks', (r: OrchestrationTask) => r.
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
getByTaskMasterId(taskmasterId: string): OrchestrationTask | undefined {
|
|
28
|
-
return this.store.findWhere('orchestration_tasks', (r: OrchestrationTask) => r.taskmasterId === taskmasterId) ?? undefined;
|
|
23
|
+
getByHermesTaskId(hermesTaskId: string): OrchestrationTask | undefined {
|
|
24
|
+
return this.store.findWhere('orchestration_tasks', (r: OrchestrationTask) => r.hermesTaskId === hermesTaskId) ?? undefined;
|
|
29
25
|
}
|
|
30
26
|
|
|
31
27
|
list(projectId?: string): OrchestrationTask[] {
|
|
@@ -15,35 +15,16 @@ export function createOrchestrationTaskRouter(): Router {
|
|
|
15
15
|
const projectId = typeof req.body?.projectId === 'string' ? req.body.projectId : 'default';
|
|
16
16
|
const title = typeof req.body?.title === 'string' ? req.body.title.trim() : '';
|
|
17
17
|
const description = typeof req.body?.description === 'string' ? req.body.description : undefined;
|
|
18
|
-
const taskmasterId = typeof req.body?.taskmasterId === 'string' ? req.body.taskmasterId : undefined;
|
|
19
18
|
|
|
20
19
|
if (!title) {
|
|
21
20
|
res.status(400).json({ error: { code: 'TITLE_REQUIRED', message: 'title is required' } });
|
|
22
21
|
return;
|
|
23
22
|
}
|
|
24
23
|
|
|
25
|
-
const task = orchestrationTaskService.create({ projectId, title, description
|
|
24
|
+
const task = orchestrationTaskService.create({ projectId, title, description });
|
|
26
25
|
res.status(201).json(task);
|
|
27
26
|
});
|
|
28
27
|
|
|
29
|
-
router.post('/tasks/import-taskmaster', (req, res) => {
|
|
30
|
-
const projectId = typeof req.body?.projectId === 'string' ? req.body.projectId : 'default';
|
|
31
|
-
const entries = Array.isArray(req.body?.tasks) ? req.body.tasks : [];
|
|
32
|
-
const imported = entries
|
|
33
|
-
.map((entry: unknown) => {
|
|
34
|
-
if (!entry || typeof entry !== 'object') return null;
|
|
35
|
-
const record = entry as Record<string, unknown>;
|
|
36
|
-
const taskmasterId = typeof record.id === 'string' ? record.id : undefined;
|
|
37
|
-
const title = typeof record.title === 'string' ? record.title : undefined;
|
|
38
|
-
const description = typeof record.description === 'string' ? record.description : undefined;
|
|
39
|
-
if (!taskmasterId || !title) return null;
|
|
40
|
-
return orchestrationTaskService.upsertFromTaskMaster({ projectId, title, description, taskmasterId });
|
|
41
|
-
})
|
|
42
|
-
.filter(Boolean);
|
|
43
|
-
|
|
44
|
-
res.json({ tasks: imported, count: imported.length });
|
|
45
|
-
});
|
|
46
|
-
|
|
47
28
|
router.post('/tasks/:id/dispatch', async (req, res) => {
|
|
48
29
|
try {
|
|
49
30
|
const adapterId = typeof req.body?.adapterId === 'string' ? req.body.adapterId : '';
|
|
@@ -79,7 +79,6 @@ class OrchestrationTaskService {
|
|
|
79
79
|
projectId: input.projectId,
|
|
80
80
|
title: input.title,
|
|
81
81
|
description: input.description,
|
|
82
|
-
taskmasterId: input.taskmasterId,
|
|
83
82
|
acceptanceCriteria: input.acceptanceCriteria,
|
|
84
83
|
changedFiles: input.changedFiles,
|
|
85
84
|
state: 'todo',
|
|
@@ -90,27 +89,11 @@ class OrchestrationTaskService {
|
|
|
90
89
|
return task;
|
|
91
90
|
}
|
|
92
91
|
|
|
93
|
-
upsertFromTaskMaster(input: CreateOrchestrationTaskInput): OrchestrationTask {
|
|
94
|
-
const existing = this.store.list(input.projectId).find((task) =>
|
|
95
|
-
task.taskmasterId === input.taskmasterId,
|
|
96
|
-
);
|
|
97
|
-
if (existing) {
|
|
98
|
-
existing.title = input.title;
|
|
99
|
-
existing.description = input.description;
|
|
100
|
-
existing.acceptanceCriteria = input.acceptanceCriteria ?? existing.acceptanceCriteria;
|
|
101
|
-
existing.changedFiles = uniqueStrings([...(existing.changedFiles ?? []), ...(input.changedFiles ?? [])]);
|
|
102
|
-
existing.updatedAt = Date.now();
|
|
103
|
-
this.store.set(existing);
|
|
104
|
-
return existing;
|
|
105
|
-
}
|
|
106
|
-
return this.create(input);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
92
|
async dispatch(taskId: string, input: DispatchOrchestrationTaskInput): Promise<OrchestrationTask> {
|
|
110
93
|
const task = this.store.get(taskId);
|
|
111
94
|
if (!task) throw new Error('TASK_NOT_FOUND');
|
|
112
95
|
|
|
113
|
-
const a2aResponse = await fetch(`http://127.0.0.1:${process.env.SERVER_PORT ?? '3001'}/
|
|
96
|
+
const a2aResponse = await fetch(`http://127.0.0.1:${process.env.SERVER_PORT ?? '3001'}/hermes/tasks`, {
|
|
114
97
|
method: 'POST',
|
|
115
98
|
headers: { 'Content-Type': 'application/json' },
|
|
116
99
|
body: JSON.stringify({
|
|
@@ -129,7 +112,6 @@ class OrchestrationTaskService {
|
|
|
129
112
|
projectPath: input.projectPath,
|
|
130
113
|
},
|
|
131
114
|
orchestrationTaskId: task.id,
|
|
132
|
-
taskmasterId: task.taskmasterId,
|
|
133
115
|
},
|
|
134
116
|
}),
|
|
135
117
|
});
|
|
@@ -139,7 +121,7 @@ class OrchestrationTaskService {
|
|
|
139
121
|
throw new Error(body?.error?.message ?? 'DISPATCH_FAILED');
|
|
140
122
|
}
|
|
141
123
|
|
|
142
|
-
task.
|
|
124
|
+
task.hermesTaskId = body.id;
|
|
143
125
|
task.adapterId = input.adapterId;
|
|
144
126
|
task.adapterSelector = input.adapterId;
|
|
145
127
|
task.workspaceKind = input.isolation ?? 'worktree';
|
|
@@ -163,12 +145,7 @@ class OrchestrationTaskService {
|
|
|
163
145
|
updateFromWorkflowRun(run: WorkflowRun): OrchestrationTask | undefined {
|
|
164
146
|
const metadata = run.metadata ?? {};
|
|
165
147
|
const taskId = readString(metadata.orchestrationTaskId);
|
|
166
|
-
const
|
|
167
|
-
const task = taskId
|
|
168
|
-
? this.store.get(taskId)
|
|
169
|
-
: taskmasterId
|
|
170
|
-
? this.store.list(readString(metadata.projectId)).find((candidate) => candidate.taskmasterId === taskmasterId)
|
|
171
|
-
: undefined;
|
|
148
|
+
const task = taskId ? this.store.get(taskId) : undefined;
|
|
172
149
|
if (!task) return undefined;
|
|
173
150
|
|
|
174
151
|
const changedFiles = changedFilesFromWorkflowRun(run);
|
|
@@ -187,9 +164,6 @@ class OrchestrationTaskService {
|
|
|
187
164
|
task.updatedAt = Date.now();
|
|
188
165
|
this.store.set(task);
|
|
189
166
|
|
|
190
|
-
if (task.taskmasterId && task.state === 'done') {
|
|
191
|
-
this.syncTaskMasterStatus(task.taskmasterId, 'done');
|
|
192
|
-
}
|
|
193
167
|
return task;
|
|
194
168
|
}
|
|
195
169
|
|
|
@@ -207,7 +181,7 @@ class OrchestrationTaskService {
|
|
|
207
181
|
if (event.kind !== 'task-state') return;
|
|
208
182
|
if (!TERMINAL_A2A_STATES.includes(event.state)) return;
|
|
209
183
|
|
|
210
|
-
const orchTask = this.store.
|
|
184
|
+
const orchTask = this.store.getByHermesTaskId(event.taskId);
|
|
211
185
|
if (!orchTask) return;
|
|
212
186
|
if (orchTask.state === 'done' || orchTask.state === 'failed' || orchTask.state === 'canceled') return;
|
|
213
187
|
|
|
@@ -219,20 +193,9 @@ class OrchestrationTaskService {
|
|
|
219
193
|
orchTask.updatedAt = Date.now();
|
|
220
194
|
this.store.set(orchTask);
|
|
221
195
|
|
|
222
|
-
if (orchTask.taskmasterId && mapped === 'done') {
|
|
223
|
-
this.syncTaskMasterStatus(orchTask.taskmasterId, 'done');
|
|
224
|
-
}
|
|
225
196
|
});
|
|
226
197
|
}
|
|
227
198
|
|
|
228
|
-
private syncTaskMasterStatus(taskmasterId: string, status: string): void {
|
|
229
|
-
const { spawn } = require('node:child_process') as typeof import('node:child_process');
|
|
230
|
-
spawn('task-master', ['set-status', '--id', taskmasterId, '--status', status], {
|
|
231
|
-
stdio: 'ignore',
|
|
232
|
-
detached: true,
|
|
233
|
-
}).unref();
|
|
234
|
-
}
|
|
235
|
-
|
|
236
199
|
cancel(taskId: string): OrchestrationTask {
|
|
237
200
|
const task = this.store.get(taskId);
|
|
238
201
|
if (!task) throw new Error('TASK_NOT_FOUND');
|
|
@@ -2,9 +2,8 @@ export type OrchestrationTaskState = 'todo' | 'in_progress' | 'in_review' | 'don
|
|
|
2
2
|
|
|
3
3
|
export interface OrchestrationTask {
|
|
4
4
|
id: string;
|
|
5
|
-
|
|
5
|
+
hermesTaskId?: string;
|
|
6
6
|
workflowRunIds?: string[];
|
|
7
|
-
taskmasterId?: string;
|
|
8
7
|
projectId: string;
|
|
9
8
|
title: string;
|
|
10
9
|
description?: string;
|
|
@@ -13,7 +12,7 @@ export interface OrchestrationTask {
|
|
|
13
12
|
id: string;
|
|
14
13
|
label: string;
|
|
15
14
|
status: 'pending' | 'passed' | 'failed';
|
|
16
|
-
source: '
|
|
15
|
+
source: 'workflow' | 'hermes';
|
|
17
16
|
}>;
|
|
18
17
|
changedFiles?: string[];
|
|
19
18
|
adapterId?: string;
|
|
@@ -28,7 +27,6 @@ export interface CreateOrchestrationTaskInput {
|
|
|
28
27
|
projectId: string;
|
|
29
28
|
title: string;
|
|
30
29
|
description?: string;
|
|
31
|
-
taskmasterId?: string;
|
|
32
30
|
acceptanceCriteria?: OrchestrationTask['acceptanceCriteria'];
|
|
33
31
|
changedFiles?: string[];
|
|
34
32
|
}
|
|
@@ -11,7 +11,7 @@ export interface TaskRunGraphCriterion {
|
|
|
11
11
|
id: string;
|
|
12
12
|
label: string;
|
|
13
13
|
status: TaskRunGraphCriterionStatus;
|
|
14
|
-
source: '
|
|
14
|
+
source: 'workflow' | 'hermes';
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
export interface TaskRunGraphRunSummary {
|
|
@@ -20,7 +20,6 @@ export interface TaskRunGraphRunSummary {
|
|
|
20
20
|
status: WorkflowRun['status'];
|
|
21
21
|
startedAt: number;
|
|
22
22
|
finishedAt?: number;
|
|
23
|
-
taskmasterId?: string;
|
|
24
23
|
orchestrationTaskId?: string;
|
|
25
24
|
changedFiles: string[];
|
|
26
25
|
}
|
|
@@ -28,7 +27,6 @@ export interface TaskRunGraphRunSummary {
|
|
|
28
27
|
export interface TaskRunGraph {
|
|
29
28
|
protocol: typeof PIXCODE_TASK_RUN_GRAPH_PROTOCOL;
|
|
30
29
|
projectId: string;
|
|
31
|
-
taskmasterId?: string;
|
|
32
30
|
orchestrationTaskId?: string;
|
|
33
31
|
workflowRuns: TaskRunGraphRunSummary[];
|
|
34
32
|
changedFiles: string[];
|
|
@@ -55,59 +53,6 @@ function uniqueStrings(values: Array<string | undefined>): string[] {
|
|
|
55
53
|
.sort((a, b) => a.localeCompare(b));
|
|
56
54
|
}
|
|
57
55
|
|
|
58
|
-
function taskStatusPassed(status: unknown): boolean {
|
|
59
|
-
const normalized = String(status ?? '').toLocaleLowerCase('en');
|
|
60
|
-
return normalized === 'done' || normalized === 'completed';
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function taskStatusFailed(status: unknown): boolean {
|
|
64
|
-
const normalized = String(status ?? '').toLocaleLowerCase('en');
|
|
65
|
-
return normalized === 'failed' || normalized === 'blocked' || normalized === 'cancelled' || normalized === 'canceled';
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function criterionStatus(status: unknown): TaskRunGraphCriterionStatus {
|
|
69
|
-
if (taskStatusPassed(status)) return 'passed';
|
|
70
|
-
if (taskStatusFailed(status)) return 'failed';
|
|
71
|
-
return 'pending';
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
function taskmasterAcceptanceCriteria(taskmasterTask: Record<string, unknown> | undefined): TaskRunGraphCriterion[] {
|
|
75
|
-
if (!taskmasterTask) return [];
|
|
76
|
-
|
|
77
|
-
const criteria: TaskRunGraphCriterion[] = [];
|
|
78
|
-
const testStrategy = readString(taskmasterTask.testStrategy) ?? readString(taskmasterTask.test_strategy);
|
|
79
|
-
if (testStrategy) {
|
|
80
|
-
criteria.push({
|
|
81
|
-
id: 'test-strategy',
|
|
82
|
-
label: testStrategy,
|
|
83
|
-
status: criterionStatus(taskmasterTask.status),
|
|
84
|
-
source: 'taskmaster',
|
|
85
|
-
});
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
const subtasks = Array.isArray(taskmasterTask.subtasks) ? taskmasterTask.subtasks : [];
|
|
89
|
-
subtasks.forEach((subtask, index) => {
|
|
90
|
-
const record = readRecord(subtask);
|
|
91
|
-
if (!record) return;
|
|
92
|
-
const title = readString(record.title);
|
|
93
|
-
if (!title) return;
|
|
94
|
-
criteria.push({
|
|
95
|
-
id: `subtask-${readString(record.id) ?? index + 1}`,
|
|
96
|
-
label: title,
|
|
97
|
-
status: criterionStatus(record.status),
|
|
98
|
-
source: 'taskmaster',
|
|
99
|
-
});
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
return criteria;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
function metadataTaskmasterId(run: WorkflowRun): string | undefined {
|
|
106
|
-
return readString(run.metadata?.taskmasterId)
|
|
107
|
-
?? readString(readRecord(run.metadata?.taskGraph)?.taskmasterId)
|
|
108
|
-
?? readString(readRecord(run.metadata?.replay)?.taskmasterId);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
56
|
function metadataOrchestrationTaskId(run: WorkflowRun): string | undefined {
|
|
112
57
|
return readString(run.metadata?.orchestrationTaskId)
|
|
113
58
|
?? readString(readRecord(run.metadata?.taskGraph)?.orchestrationTaskId);
|
|
@@ -146,10 +91,8 @@ export function changedFilesFromWorkflowRun(run: WorkflowRun): string[] {
|
|
|
146
91
|
return uniqueStrings(run.nodeRuns.flatMap((node) => artifactChangedFiles(node)));
|
|
147
92
|
}
|
|
148
93
|
|
|
149
|
-
function workflowRunMatchesTask(run: WorkflowRun, task: OrchestrationTask | undefined
|
|
150
|
-
const runTaskmasterId = metadataTaskmasterId(run);
|
|
94
|
+
function workflowRunMatchesTask(run: WorkflowRun, task: OrchestrationTask | undefined): boolean {
|
|
151
95
|
const runOrchestrationTaskId = metadataOrchestrationTaskId(run);
|
|
152
|
-
if (taskmasterId && runTaskmasterId === taskmasterId) return true;
|
|
153
96
|
if (task?.id && runOrchestrationTaskId === task.id) return true;
|
|
154
97
|
if (task?.workflowRunIds?.includes(run.id)) return true;
|
|
155
98
|
return false;
|
|
@@ -162,7 +105,6 @@ function runSummary(run: WorkflowRun): TaskRunGraphRunSummary {
|
|
|
162
105
|
status: run.status,
|
|
163
106
|
startedAt: run.startedAt,
|
|
164
107
|
finishedAt: run.finishedAt,
|
|
165
|
-
taskmasterId: metadataTaskmasterId(run),
|
|
166
108
|
orchestrationTaskId: metadataOrchestrationTaskId(run),
|
|
167
109
|
changedFiles: changedFilesFromWorkflowRun(run),
|
|
168
110
|
};
|
|
@@ -170,19 +112,15 @@ function runSummary(run: WorkflowRun): TaskRunGraphRunSummary {
|
|
|
170
112
|
|
|
171
113
|
export function buildTaskRunGraph({
|
|
172
114
|
projectId,
|
|
173
|
-
|
|
174
|
-
taskmasterTask,
|
|
115
|
+
orchestrationTaskId,
|
|
175
116
|
}: {
|
|
176
117
|
projectId: string;
|
|
177
|
-
|
|
178
|
-
taskmasterTask?: Record<string, unknown>;
|
|
118
|
+
orchestrationTaskId?: string;
|
|
179
119
|
}): TaskRunGraph {
|
|
180
|
-
const orchestrationTask =
|
|
181
|
-
? orchestrationTaskService.list(projectId).find((task) => task.taskmasterId === taskmasterId)
|
|
182
|
-
: undefined;
|
|
120
|
+
const orchestrationTask = orchestrationTaskId ? orchestrationTaskService.get(orchestrationTaskId) : undefined;
|
|
183
121
|
const workflowRuns = workflowStore
|
|
184
122
|
.listRuns()
|
|
185
|
-
.filter((run) => workflowRunMatchesTask(run, orchestrationTask
|
|
123
|
+
.filter((run) => workflowRunMatchesTask(run, orchestrationTask))
|
|
186
124
|
.map(runSummary);
|
|
187
125
|
const workflowCriteria: TaskRunGraphCriterion[] = workflowRuns.map((run) => ({
|
|
188
126
|
id: `run-${run.id}`,
|
|
@@ -191,7 +129,6 @@ export function buildTaskRunGraph({
|
|
|
191
129
|
source: 'workflow',
|
|
192
130
|
}));
|
|
193
131
|
const acceptanceCriteria = [
|
|
194
|
-
...taskmasterAcceptanceCriteria(taskmasterTask),
|
|
195
132
|
...(orchestrationTask?.acceptanceCriteria ?? []),
|
|
196
133
|
...workflowCriteria,
|
|
197
134
|
];
|
|
@@ -203,7 +140,6 @@ export function buildTaskRunGraph({
|
|
|
203
140
|
return {
|
|
204
141
|
protocol: PIXCODE_TASK_RUN_GRAPH_PROTOCOL,
|
|
205
142
|
projectId,
|
|
206
|
-
taskmasterId,
|
|
207
143
|
orchestrationTaskId: orchestrationTask?.id,
|
|
208
144
|
workflowRuns,
|
|
209
145
|
changedFiles,
|