@pixelbyte-software/pixcode 1.36.4 → 1.38.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-D-YjltED.js → index-C-gVa0Gf.js} +163 -157
- package/dist/assets/index-CfHK8y_H.css +32 -0
- package/dist/index.html +2 -2
- package/dist-server/server/database/db.js +4 -2
- package/dist-server/server/database/db.js.map +1 -1
- package/dist-server/server/index.js +8 -0
- package/dist-server/server/index.js.map +1 -1
- package/dist-server/server/modules/orchestration/tasks/orchestration-task.routes.js +10 -1
- 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 +7 -0
- package/dist-server/server/modules/orchestration/tasks/orchestration-task.service.js.map +1 -1
- package/dist-server/server/modules/orchestration/workflows/workflow-runner.js +127 -24
- package/dist-server/server/modules/orchestration/workflows/workflow-runner.js.map +1 -1
- package/dist-server/server/routes/diagnostics.js +12 -0
- package/dist-server/server/routes/diagnostics.js.map +1 -0
- package/dist-server/server/routes/taskmaster.js +194 -0
- package/dist-server/server/routes/taskmaster.js.map +1 -1
- package/dist-server/server/services/diagnostics.js +91 -0
- package/dist-server/server/services/diagnostics.js.map +1 -0
- package/dist-server/server/services/install-jobs.js +1 -0
- package/dist-server/server/services/install-jobs.js.map +1 -1
- package/dist-server/server/services/notification-orchestrator.js +66 -9
- package/dist-server/server/services/notification-orchestrator.js.map +1 -1
- package/dist-server/server/services/telegram/control-center.js +144 -2
- package/dist-server/server/services/telegram/control-center.js.map +1 -1
- package/dist-server/server/services/telegram/translations.js +14 -2
- package/dist-server/server/services/telegram/translations.js.map +1 -1
- package/package.json +5 -1
- package/scripts/github/create-v1.38-issues.mjs +351 -0
- package/scripts/smoke/chat-realtime-hydration.mjs +44 -0
- package/scripts/smoke/discord-release-workflow.mjs +24 -0
- package/scripts/smoke/multi-worker-slots.mjs +42 -0
- package/scripts/smoke/notification-center.mjs +63 -0
- package/scripts/smoke/orchestration-execution-dashboard.mjs +33 -0
- package/scripts/smoke/strict-handoff-compact.mjs +60 -0
- package/scripts/smoke/taskmaster-execution-telegram.mjs +52 -0
- package/scripts/smoke/taskmaster-onboarding.mjs +52 -0
- package/scripts/smoke/update-issue-progress.mjs +69 -0
- package/scripts/smoke/v138-desktop-release-hardening.mjs +69 -0
- package/scripts/smoke/v138-diagnostics.mjs +63 -0
- package/scripts/smoke/v138-issue-planner.mjs +33 -0
- package/server/database/db.js +4 -2
- package/server/index.js +9 -0
- package/server/modules/orchestration/tasks/orchestration-task.routes.ts +10 -1
- package/server/modules/orchestration/tasks/orchestration-task.service.ts +7 -0
- package/server/modules/orchestration/tasks/orchestration-task.types.ts +3 -0
- package/server/modules/orchestration/workflows/workflow-runner.ts +132 -24
- package/server/modules/orchestration/workflows/workflow.types.ts +2 -0
- package/server/routes/diagnostics.js +15 -0
- package/server/routes/taskmaster.js +201 -0
- package/server/services/diagnostics.js +105 -0
- package/server/services/install-jobs.js +1 -0
- package/server/services/notification-orchestrator.js +76 -8
- package/server/services/telegram/control-center.js +153 -2
- package/server/services/telegram/translations.js +14 -2
- package/dist/assets/index-CgF0-_6Z.css +0 -32
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import assert from 'node:assert/strict';
|
|
4
|
+
import { readFileSync } from 'node:fs';
|
|
5
|
+
|
|
6
|
+
const installJobs = readFileSync('server/services/install-jobs.js', 'utf8');
|
|
7
|
+
const taskmasterRoutes = readFileSync('server/routes/taskmaster.js', 'utf8');
|
|
8
|
+
const onboarding = readFileSync('src/components/onboarding/view/Onboarding.tsx', 'utf8');
|
|
9
|
+
const stepProgress = readFileSync('src/components/onboarding/view/subcomponents/OnboardingStepProgress.tsx', 'utf8');
|
|
10
|
+
const taskStep = readFileSync('src/components/onboarding/view/subcomponents/TaskSystemStep.tsx', 'utf8');
|
|
11
|
+
|
|
12
|
+
assert.ok(
|
|
13
|
+
installJobs.includes("'task-master': 'task-master'"),
|
|
14
|
+
'TaskMaster package should be verified by the sandbox CLI installer.',
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
assert.ok(
|
|
18
|
+
taskmasterRoutes.includes("router.post('/install'"),
|
|
19
|
+
'TaskMaster routes should expose an authenticated install endpoint.',
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
assert.ok(
|
|
23
|
+
taskmasterRoutes.includes("router.get('/install/:jobId/stream'"),
|
|
24
|
+
'TaskMaster install should expose the same resilient log stream pattern as provider installs.',
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
assert.ok(
|
|
28
|
+
taskmasterRoutes.includes("provider: 'taskmaster'") && taskmasterRoutes.includes("packageName: 'task-master'"),
|
|
29
|
+
'TaskMaster install route should install the task-master npm package under the taskmaster job provider.',
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
assert.ok(
|
|
33
|
+
onboarding.includes('TaskSystemStep') && onboarding.includes('currentStep < 2'),
|
|
34
|
+
'Onboarding should include a third Task system step before completion.',
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
assert.ok(
|
|
38
|
+
onboarding.includes("localStorage.setItem('tasks-enabled'"),
|
|
39
|
+
'Onboarding should persist the user task-system choice.',
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
assert.ok(
|
|
43
|
+
stepProgress.includes('Task System'),
|
|
44
|
+
'Onboarding progress should show the Task System step.',
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
assert.ok(
|
|
48
|
+
taskStep.includes('/api/taskmaster/installation-status') && taskStep.includes('/api/taskmaster/install'),
|
|
49
|
+
'TaskSystemStep should check and install TaskMaster through the backend API.',
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
console.log('taskmaster onboarding smoke passed');
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import assert from 'node:assert/strict';
|
|
4
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
5
|
+
|
|
6
|
+
const parserPath = 'src/components/version-upgrade/utils/releaseIssueProgress.ts';
|
|
7
|
+
const componentPath = 'src/components/version-upgrade/view/ReleaseIssueProgress.tsx';
|
|
8
|
+
const modalPath = 'src/components/version-upgrade/view/VersionUpgradeModal.tsx';
|
|
9
|
+
const trackingPath = 'RELEASE_TRACKING_v1.37.md';
|
|
10
|
+
|
|
11
|
+
assert.ok(existsSync(parserPath), 'Release issue progress parser should exist.');
|
|
12
|
+
assert.ok(existsSync(componentPath), 'Release issue progress component should exist.');
|
|
13
|
+
assert.ok(existsSync(trackingPath), 'v1.37 release tracking document should exist.');
|
|
14
|
+
|
|
15
|
+
const parserSource = readFileSync(parserPath, 'utf8');
|
|
16
|
+
assert.ok(
|
|
17
|
+
parserSource.includes('RELEASE_ISSUE_PROGRESS_MARKER'),
|
|
18
|
+
'Parser should expose a stable release-note marker for issue progress blocks.',
|
|
19
|
+
);
|
|
20
|
+
assert.ok(
|
|
21
|
+
parserSource.includes('extractIssueProgress'),
|
|
22
|
+
'Parser should export extractIssueProgress for release body parsing.',
|
|
23
|
+
);
|
|
24
|
+
assert.ok(
|
|
25
|
+
parserSource.includes('DEFAULT_V137_ISSUE_PROGRESS'),
|
|
26
|
+
'Parser should provide a v1.37 fallback issue map until the release body is published.',
|
|
27
|
+
);
|
|
28
|
+
assert.ok(
|
|
29
|
+
parserSource.includes('\\[([xX ~-])\\]') || parserSource.includes('[x]'),
|
|
30
|
+
'Parser should understand checked issue/task rows.',
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
const componentSource = readFileSync(componentPath, 'utf8');
|
|
34
|
+
assert.ok(
|
|
35
|
+
componentSource.includes('extractIssueProgress'),
|
|
36
|
+
'Component should render parsed issue progress from release notes.',
|
|
37
|
+
);
|
|
38
|
+
assert.ok(
|
|
39
|
+
componentSource.includes('completedCount'),
|
|
40
|
+
'Component should summarize completed issue progress.',
|
|
41
|
+
);
|
|
42
|
+
assert.ok(
|
|
43
|
+
componentSource.includes("version?.startsWith('1.37')") || componentSource.includes('version.startsWith'),
|
|
44
|
+
'Component should only use the v1.37 fallback for v1.37 release notes.',
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
const modalSource = readFileSync(modalPath, 'utf8');
|
|
48
|
+
assert.ok(
|
|
49
|
+
modalSource.includes('ReleaseIssueProgress'),
|
|
50
|
+
'Version modal should include release issue progress.',
|
|
51
|
+
);
|
|
52
|
+
assert.ok(
|
|
53
|
+
modalSource.includes('releaseInfo.body'),
|
|
54
|
+
'Version modal should pass release body text to the issue progress view.',
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
const trackingSource = readFileSync(trackingPath, 'utf8');
|
|
58
|
+
for (const issueNumber of [6, 7, 8, 9, 10, 11, 12, 13, 14]) {
|
|
59
|
+
assert.ok(
|
|
60
|
+
trackingSource.includes(`#${issueNumber}`),
|
|
61
|
+
`Release tracking should mention issue #${issueNumber}.`,
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
assert.ok(
|
|
65
|
+
trackingSource.includes('<!-- pixcode:issue-progress -->'),
|
|
66
|
+
'Release tracking should include the issue-progress marker block used in release notes.',
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
console.log('update issue progress smoke passed');
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
|
|
4
|
+
function readText(path) {
|
|
5
|
+
return fs.readFileSync(path, 'utf8');
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function readJson(path) {
|
|
9
|
+
return JSON.parse(readText(path));
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function assertIncludes(path, patterns) {
|
|
13
|
+
const content = readText(path);
|
|
14
|
+
for (const pattern of patterns) {
|
|
15
|
+
if (!content.includes(pattern)) {
|
|
16
|
+
throw new Error(`${path} is missing required release-hardening text: ${pattern}`);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const rootPackage = readJson('package.json');
|
|
22
|
+
const desktopPackage = readJson('desktop/package.json');
|
|
23
|
+
const bundledPixcodeVersion = desktopPackage.dependencies?.['@pixelbyte-software/pixcode'];
|
|
24
|
+
|
|
25
|
+
if (desktopPackage.version !== rootPackage.version) {
|
|
26
|
+
throw new Error(`desktop package version ${desktopPackage.version} does not match root ${rootPackage.version}`);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (bundledPixcodeVersion !== rootPackage.version) {
|
|
30
|
+
throw new Error(`desktop bundled pixcode version ${bundledPixcodeVersion} does not match root ${rootPackage.version}`);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
assertIncludes('README.md', [
|
|
34
|
+
'macOS Gatekeeper: "Pixcode is damaged"',
|
|
35
|
+
'Fix Gatekeeper.command',
|
|
36
|
+
'xattr -dr com.apple.quarantine',
|
|
37
|
+
'unsigned',
|
|
38
|
+
]);
|
|
39
|
+
|
|
40
|
+
assertIncludes('README.tr.md', [
|
|
41
|
+
'macOS Gatekeeper: "Pixcode hasar görmüş"',
|
|
42
|
+
'Fix Gatekeeper.command',
|
|
43
|
+
'xattr -dr com.apple.quarantine',
|
|
44
|
+
'imzalı/notarize değil',
|
|
45
|
+
]);
|
|
46
|
+
|
|
47
|
+
assertIncludes('desktop/README.md', [
|
|
48
|
+
'macOS Gatekeeper fix',
|
|
49
|
+
'Fix Gatekeeper.command',
|
|
50
|
+
'xattr -dr com.apple.quarantine',
|
|
51
|
+
'unsigned',
|
|
52
|
+
]);
|
|
53
|
+
|
|
54
|
+
assertIncludes('desktop/electron-builder.yml', [
|
|
55
|
+
'Pixcode-Setup-${version}.${ext}',
|
|
56
|
+
'Pixcode-${version}-${arch}.${ext}',
|
|
57
|
+
'target: AppImage',
|
|
58
|
+
'build-resources/Fix Gatekeeper.command',
|
|
59
|
+
'identity: null',
|
|
60
|
+
]);
|
|
61
|
+
|
|
62
|
+
assertIncludes('desktop/build-resources/Fix Gatekeeper.command', [
|
|
63
|
+
'Pixcode Gatekeeper Fix',
|
|
64
|
+
'xattr -cr "$APP_PATH"',
|
|
65
|
+
'com.apple.quarantine',
|
|
66
|
+
'open "$APP_PATH"',
|
|
67
|
+
]);
|
|
68
|
+
|
|
69
|
+
console.log('v1.38 desktop release hardening smoke passed');
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { collectDiagnostics } from '../../server/services/diagnostics.js';
|
|
3
|
+
|
|
4
|
+
const diagnostics = collectDiagnostics({
|
|
5
|
+
now: new Date('2026-05-10T00:00:00.000Z'),
|
|
6
|
+
env: {
|
|
7
|
+
NODE_ENV: 'test',
|
|
8
|
+
SERVER_PORT: '3001',
|
|
9
|
+
GITHUB_TOKEN: 'ghp_super_secret_value',
|
|
10
|
+
NPM_TOKEN: 'npm_super_secret_value',
|
|
11
|
+
TELEGRAM_BOT_TOKEN: 'telegram_super_secret_value',
|
|
12
|
+
},
|
|
13
|
+
uptime: 42,
|
|
14
|
+
memoryUsage: () => ({
|
|
15
|
+
rss: 10,
|
|
16
|
+
heapTotal: 20,
|
|
17
|
+
heapUsed: 5,
|
|
18
|
+
external: 1,
|
|
19
|
+
arrayBuffers: 0,
|
|
20
|
+
}),
|
|
21
|
+
versions: {
|
|
22
|
+
node: '22.0.0',
|
|
23
|
+
v8: '12.0.0',
|
|
24
|
+
},
|
|
25
|
+
platform: 'linux',
|
|
26
|
+
arch: 'x64',
|
|
27
|
+
installMode: 'git',
|
|
28
|
+
serverVersion: '1.38.0',
|
|
29
|
+
wsClientCount: 3,
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const raw = JSON.stringify(diagnostics);
|
|
33
|
+
for (const secret of ['ghp_super_secret_value', 'npm_super_secret_value', 'telegram_super_secret_value']) {
|
|
34
|
+
if (raw.includes(secret)) {
|
|
35
|
+
throw new Error(`diagnostics leaked secret value: ${secret}`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (diagnostics.status !== 'ok') {
|
|
40
|
+
throw new Error(`expected ok diagnostics status, got ${diagnostics.status}`);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (diagnostics.timestamp !== '2026-05-10T00:00:00.000Z') {
|
|
44
|
+
throw new Error(`unexpected diagnostics timestamp ${diagnostics.timestamp}`);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (diagnostics.version !== '1.38.0' || diagnostics.installMode !== 'git') {
|
|
48
|
+
throw new Error('diagnostics did not include injected version/install mode');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (diagnostics.websocket.clients !== 3) {
|
|
52
|
+
throw new Error(`expected 3 websocket clients, got ${diagnostics.websocket.clients}`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (diagnostics.environment.GITHUB_TOKEN !== '[redacted]') {
|
|
56
|
+
throw new Error('GITHUB_TOKEN was not redacted');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (diagnostics.environment.NODE_ENV !== 'test') {
|
|
60
|
+
throw new Error('safe environment value was not preserved');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
console.log('v1.38 diagnostics smoke passed');
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { buildPayload } from '../github/create-v1.38-issues.mjs';
|
|
3
|
+
|
|
4
|
+
const payload = buildPayload();
|
|
5
|
+
|
|
6
|
+
if (payload.version !== '1.38') {
|
|
7
|
+
throw new Error(`expected version 1.38, got ${payload.version}`);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
if (!Array.isArray(payload.issues) || payload.issues.length !== 7) {
|
|
11
|
+
throw new Error(`expected 7 child issues, got ${payload.issues?.length}`);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
if (!payload.epic || !payload.epic.title.includes('v1.38')) {
|
|
15
|
+
throw new Error('missing v1.38 epic payload');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const expectedKeys = ['remote', 'api', 'telegram', 'taskmaster', 'plugins', 'desktop', 'observability'];
|
|
19
|
+
for (const key of expectedKeys) {
|
|
20
|
+
const issue = payload.issues.find(item => item.key === key);
|
|
21
|
+
if (!issue) {
|
|
22
|
+
throw new Error(`missing issue key ${key}`);
|
|
23
|
+
}
|
|
24
|
+
if (!issue.title || !issue.body.includes('## Acceptance Criteria')) {
|
|
25
|
+
throw new Error(`issue ${key} is missing title or acceptance criteria`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (!payload.trackingReplacements || payload.trackingReplacements.length !== 8) {
|
|
30
|
+
throw new Error('expected replacement plan for 7 issues plus epic');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
console.log('v1.38 issue planner smoke passed');
|
package/server/database/db.js
CHANGED
|
@@ -493,7 +493,7 @@ const credentialsDb = {
|
|
|
493
493
|
// Notification preferences
|
|
494
494
|
// ---------------------------------------------------------------------------
|
|
495
495
|
const DEFAULT_NOTIFICATION_PREFERENCES = {
|
|
496
|
-
channels: { inApp:
|
|
496
|
+
channels: { inApp: true, webPush: false, telegram: true, desktop: true },
|
|
497
497
|
events: { actionRequired: true, stop: true, error: true, updates: true },
|
|
498
498
|
};
|
|
499
499
|
|
|
@@ -501,8 +501,10 @@ const normalizeNotificationPreferences = (value) => {
|
|
|
501
501
|
const source = value && typeof value === 'object' ? value : {};
|
|
502
502
|
return {
|
|
503
503
|
channels: {
|
|
504
|
-
inApp: source.channels?.inApp
|
|
504
|
+
inApp: source.channels?.inApp !== false,
|
|
505
505
|
webPush: source.channels?.webPush === true,
|
|
506
|
+
telegram: source.channels?.telegram !== false,
|
|
507
|
+
desktop: source.channels?.desktop !== false,
|
|
506
508
|
},
|
|
507
509
|
events: {
|
|
508
510
|
actionRequired: source.events?.actionRequired !== false,
|
package/server/index.js
CHANGED
|
@@ -75,6 +75,7 @@ import geminiRoutes from './routes/gemini.js';
|
|
|
75
75
|
import qwenRoutes from './routes/qwen.js';
|
|
76
76
|
import pluginsRoutes from './routes/plugins.js';
|
|
77
77
|
import messagesRoutes from './routes/messages.js';
|
|
78
|
+
import diagnosticsRoutes from './routes/diagnostics.js';
|
|
78
79
|
import providerRoutes from './modules/providers/provider.routes.js';
|
|
79
80
|
import {
|
|
80
81
|
createA2ARouter,
|
|
@@ -99,6 +100,7 @@ import {
|
|
|
99
100
|
import { primeCliBinPath } from './services/install-jobs.js';
|
|
100
101
|
import { startEnabledPluginServers, stopAllPlugins, getPluginPort } from './utils/plugin-process-manager.js';
|
|
101
102
|
import { initializeDatabase, sessionNamesDb, applyCustomSessionNames } from './database/db.js';
|
|
103
|
+
import { setNotificationWebSocketServer } from './services/notification-orchestrator.js';
|
|
102
104
|
import { configureWebPush } from './services/vapid-keys.js';
|
|
103
105
|
import { validateApiKey, authenticateToken, authenticateWebSocket } from './middleware/auth.js';
|
|
104
106
|
import { IS_PLATFORM } from './constants/config.js';
|
|
@@ -319,6 +321,9 @@ const wss = new WebSocketServer({
|
|
|
319
321
|
|
|
320
322
|
// Make WebSocket server available to routes
|
|
321
323
|
app.locals.wss = wss;
|
|
324
|
+
app.locals.installMode = installMode;
|
|
325
|
+
app.locals.serverVersion = SERVER_VERSION;
|
|
326
|
+
setNotificationWebSocketServer(wss);
|
|
322
327
|
|
|
323
328
|
app.use(cors({ exposedHeaders: ['X-Refreshed-Token'] }));
|
|
324
329
|
app.use(express.json({
|
|
@@ -389,6 +394,9 @@ app.use('/api/plugins', authenticateToken, pluginsRoutes);
|
|
|
389
394
|
// Unified session messages route (protected)
|
|
390
395
|
app.use('/api/sessions', authenticateToken, messagesRoutes);
|
|
391
396
|
|
|
397
|
+
// Diagnostics API Routes (protected)
|
|
398
|
+
app.use('/api/diagnostics', authenticateToken, diagnosticsRoutes);
|
|
399
|
+
|
|
392
400
|
// Unified provider MCP routes (protected)
|
|
393
401
|
app.use('/api/providers', authenticateToken, providerRoutes);
|
|
394
402
|
|
|
@@ -1813,6 +1821,7 @@ function handleChatConnection(ws, request) {
|
|
|
1813
1821
|
console.log('[INFO] Chat WebSocket connected');
|
|
1814
1822
|
|
|
1815
1823
|
// Add to connected clients for project updates
|
|
1824
|
+
ws.userId = request?.user?.id ?? request?.user?.userId ?? null;
|
|
1816
1825
|
connectedClients.add(ws);
|
|
1817
1826
|
|
|
1818
1827
|
// Wrap WebSocket with writer for consistent interface with SSEStreamWriter
|
|
@@ -48,11 +48,20 @@ export function createOrchestrationTaskRouter(): Router {
|
|
|
48
48
|
try {
|
|
49
49
|
const adapterId = typeof req.body?.adapterId === 'string' ? req.body.adapterId : '';
|
|
50
50
|
const isolation = req.body?.isolation;
|
|
51
|
+
const projectPath = typeof req.body?.projectPath === 'string' ? req.body.projectPath : undefined;
|
|
52
|
+
const model = typeof req.body?.model === 'string' ? req.body.model : undefined;
|
|
53
|
+
const permissionMode = typeof req.body?.permissionMode === 'string' ? req.body.permissionMode : undefined;
|
|
51
54
|
if (!adapterId) {
|
|
52
55
|
res.status(400).json({ error: { code: 'ADAPTER_REQUIRED', message: 'adapterId is required' } });
|
|
53
56
|
return;
|
|
54
57
|
}
|
|
55
|
-
const task = await orchestrationTaskService.dispatch(req.params.id, {
|
|
58
|
+
const task = await orchestrationTaskService.dispatch(req.params.id, {
|
|
59
|
+
adapterId,
|
|
60
|
+
isolation,
|
|
61
|
+
projectPath,
|
|
62
|
+
model,
|
|
63
|
+
permissionMode,
|
|
64
|
+
});
|
|
56
65
|
res.json(task);
|
|
57
66
|
} catch (error) {
|
|
58
67
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -71,6 +71,12 @@ class OrchestrationTaskService {
|
|
|
71
71
|
},
|
|
72
72
|
metadata: {
|
|
73
73
|
isolation: input.isolation ?? 'worktree',
|
|
74
|
+
model: input.model,
|
|
75
|
+
permissionMode: input.permissionMode,
|
|
76
|
+
workspace: {
|
|
77
|
+
kind: input.isolation ?? 'worktree',
|
|
78
|
+
projectPath: input.projectPath,
|
|
79
|
+
},
|
|
74
80
|
orchestrationTaskId: task.id,
|
|
75
81
|
taskmasterId: task.taskmasterId,
|
|
76
82
|
},
|
|
@@ -86,6 +92,7 @@ class OrchestrationTaskService {
|
|
|
86
92
|
task.adapterId = input.adapterId;
|
|
87
93
|
task.adapterSelector = input.adapterId;
|
|
88
94
|
task.workspaceKind = input.isolation ?? 'worktree';
|
|
95
|
+
task.workspacePath = input.projectPath;
|
|
89
96
|
task.state = 'in_progress';
|
|
90
97
|
task.updatedAt = Date.now();
|
|
91
98
|
this.store.set(task);
|
|
@@ -299,6 +299,56 @@ function handoffPrompt(agent: AgentAssignment, role: AgentRole): string {
|
|
|
299
299
|
].filter(Boolean).join('\n');
|
|
300
300
|
}
|
|
301
301
|
|
|
302
|
+
function handoffInitPrompt(agent: AgentAssignment, index: number): string {
|
|
303
|
+
return [
|
|
304
|
+
`You are preparing ${agent.label} for a strict Pixcode handoff chain.`,
|
|
305
|
+
`This is internal step ${index + 1}.`,
|
|
306
|
+
'Create a compact init packet for the next visible work step.',
|
|
307
|
+
'Use the original user goal and any prior compact handoff packet included above.',
|
|
308
|
+
agent.instruction ? `The explicit assignment for this agent is: ${agent.instruction}` : '',
|
|
309
|
+
'Output only this internal init packet:',
|
|
310
|
+
'- user goal in one sentence',
|
|
311
|
+
'- prior agent handoff summary, if present',
|
|
312
|
+
'- this agent responsibility',
|
|
313
|
+
'- exact constraints and blockers this agent must respect',
|
|
314
|
+
privacyGuardPrompt(),
|
|
315
|
+
'Do not perform the task yet. Do not mention that this is hidden from the user.',
|
|
316
|
+
'Respond in the same language as the user request.',
|
|
317
|
+
].filter(Boolean).join('\n');
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function handoffWorkPrompt(agent: AgentAssignment, index: number): string {
|
|
321
|
+
return [
|
|
322
|
+
`You are ${agent.label} in a strict Pixcode handoff chain.`,
|
|
323
|
+
`This is visible work step ${index + 1}.`,
|
|
324
|
+
'The internal init packet above is your starting context. Do the assigned work now.',
|
|
325
|
+
agent.instruction
|
|
326
|
+
? `Your explicit assignment from the user is: ${agent.instruction}`
|
|
327
|
+
: 'Use the init packet and original user goal to choose the next useful work for this step.',
|
|
328
|
+
rolePrompt(agent.role ?? 'implementation'),
|
|
329
|
+
privacyGuardPrompt(),
|
|
330
|
+
'Report only user-facing progress, changed files, commands, verification, blockers, and next actions.',
|
|
331
|
+
'Respond in the same language as the user request.',
|
|
332
|
+
].filter(Boolean).join('\n');
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function handoffCompactPrompt(agent: AgentAssignment, index: number): string {
|
|
336
|
+
return [
|
|
337
|
+
`You are compacting ${agent.label}'s strict handoff output for the next Pixcode agent.`,
|
|
338
|
+
`This is internal compact step ${index + 1}.`,
|
|
339
|
+
'Read the prior visible work output included above and create a compact handoff packet.',
|
|
340
|
+
'Output only this internal compact packet:',
|
|
341
|
+
'- Ben ne yaptım / What I did',
|
|
342
|
+
'- Dokunduğum alanlar / Touched areas',
|
|
343
|
+
'- Kanıt, komut veya çıktı / Evidence, commands, outputs',
|
|
344
|
+
'- Sonraki ajan şunu bilsin / What the next agent must know',
|
|
345
|
+
'- Bloker veya risk / Blockers or risks',
|
|
346
|
+
privacyGuardPrompt(),
|
|
347
|
+
'Do not include raw logs unless they are essential. Keep it concise and actionable.',
|
|
348
|
+
'Respond in the same language as the user request.',
|
|
349
|
+
].join('\n');
|
|
350
|
+
}
|
|
351
|
+
|
|
302
352
|
function compactOutputForContext(text: string): string {
|
|
303
353
|
if (text.length <= MAX_OUTPUT_CONTEXT_CHARS) {
|
|
304
354
|
return text;
|
|
@@ -595,32 +645,89 @@ function expandSequentialHandoffWorkflow(workflow: Workflow, metadata?: Record<s
|
|
|
595
645
|
throw new Error('Select at least one CLI agent.');
|
|
596
646
|
}
|
|
597
647
|
|
|
648
|
+
const nodes: WorkflowNode[] = agents.flatMap((agent, index): WorkflowNode[] => {
|
|
649
|
+
const initNodeId = safeAgentNodeId(agent, index, 'init');
|
|
650
|
+
const workNodeId = safeAgentNodeId(agent, index, 'work');
|
|
651
|
+
const compactNodeId = safeAgentNodeId(agent, index, 'compact');
|
|
652
|
+
|
|
653
|
+
return [
|
|
654
|
+
{
|
|
655
|
+
id: initNodeId,
|
|
656
|
+
adapterId: agent.adapterId,
|
|
657
|
+
agentInstanceId: agent.instanceId,
|
|
658
|
+
agentLabel: `${agent.label} Init`,
|
|
659
|
+
assignment: agent.instruction,
|
|
660
|
+
stage: 'handoff_init',
|
|
661
|
+
model: agent.model,
|
|
662
|
+
permissionMode: agent.permissionMode,
|
|
663
|
+
toolsSettings: agent.toolsSettings,
|
|
664
|
+
prompt: handoffInitPrompt(agent, index),
|
|
665
|
+
inputs: index === 0 ? [] : [safeAgentNodeId(agents[index - 1], index - 1, 'compact')],
|
|
666
|
+
output: 'message',
|
|
667
|
+
onFail: 'abort',
|
|
668
|
+
internal: true,
|
|
669
|
+
},
|
|
670
|
+
{
|
|
671
|
+
id: workNodeId,
|
|
672
|
+
adapterId: agent.adapterId,
|
|
673
|
+
agentInstanceId: agent.instanceId,
|
|
674
|
+
agentLabel: agent.label,
|
|
675
|
+
assignment: agent.instruction,
|
|
676
|
+
stage: agent.role ?? 'implementation',
|
|
677
|
+
model: agent.model,
|
|
678
|
+
permissionMode: agent.permissionMode,
|
|
679
|
+
toolsSettings: agent.toolsSettings,
|
|
680
|
+
prompt: handoffWorkPrompt(agent, index),
|
|
681
|
+
inputs: [initNodeId],
|
|
682
|
+
output: 'both',
|
|
683
|
+
onFail: 'abort',
|
|
684
|
+
},
|
|
685
|
+
{
|
|
686
|
+
id: compactNodeId,
|
|
687
|
+
adapterId: agent.adapterId,
|
|
688
|
+
agentInstanceId: agent.instanceId,
|
|
689
|
+
agentLabel: `${agent.label} Compact`,
|
|
690
|
+
assignment: agent.instruction,
|
|
691
|
+
stage: 'handoff_compact',
|
|
692
|
+
model: agent.model,
|
|
693
|
+
permissionMode: agent.permissionMode,
|
|
694
|
+
toolsSettings: agent.toolsSettings,
|
|
695
|
+
prompt: handoffCompactPrompt(agent, index),
|
|
696
|
+
inputs: [workNodeId],
|
|
697
|
+
output: 'message',
|
|
698
|
+
onFail: 'abort',
|
|
699
|
+
internal: true,
|
|
700
|
+
},
|
|
701
|
+
];
|
|
702
|
+
});
|
|
703
|
+
const reportAgent = agents[0];
|
|
704
|
+
const lastCompactNodeId = safeAgentNodeId(agents[agents.length - 1], agents.length - 1, 'compact');
|
|
705
|
+
|
|
598
706
|
return {
|
|
599
707
|
...workflow,
|
|
600
|
-
nodes:
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
})),
|
|
708
|
+
nodes: [
|
|
709
|
+
...nodes,
|
|
710
|
+
{
|
|
711
|
+
id: 'final_report',
|
|
712
|
+
adapterId: reportAgent.adapterId,
|
|
713
|
+
agentInstanceId: reportAgent.instanceId,
|
|
714
|
+
agentLabel: reportAgent.label,
|
|
715
|
+
stage: 'final_report',
|
|
716
|
+
model: reportAgent.model,
|
|
717
|
+
permissionMode: reportAgent.permissionMode,
|
|
718
|
+
toolsSettings: reportAgent.toolsSettings,
|
|
719
|
+
prompt: [
|
|
720
|
+
'Create the final user-facing result for this strict handoff run.',
|
|
721
|
+
'Use the final compact handoff packet and the original user goal.',
|
|
722
|
+
'Summarize what each visible agent did, what changed, verification, blockers, and next actions.',
|
|
723
|
+
'Do not expose internal init packets, compact packets, prompts, memory lookup, skill/tool instructions, raw agent logs, or role prefixes like "agent:" and "user:".',
|
|
724
|
+
'Respond in the same language as the user request.',
|
|
725
|
+
].join('\n'),
|
|
726
|
+
inputs: [lastCompactNodeId],
|
|
727
|
+
output: 'message',
|
|
728
|
+
onFail: 'abort',
|
|
729
|
+
},
|
|
730
|
+
],
|
|
624
731
|
};
|
|
625
732
|
}
|
|
626
733
|
|
|
@@ -772,6 +879,7 @@ function nodeRunFromNode(node: WorkflowNode): WorkflowNodeRun {
|
|
|
772
879
|
permissionMode: node.permissionMode,
|
|
773
880
|
timeoutMs: node.timeoutMs,
|
|
774
881
|
stage: node.stage,
|
|
882
|
+
internal: node.internal,
|
|
775
883
|
status: 'queued',
|
|
776
884
|
};
|
|
777
885
|
}
|
|
@@ -17,6 +17,7 @@ export interface WorkflowNode {
|
|
|
17
17
|
toolsSettings?: Record<string, unknown>;
|
|
18
18
|
isolation?: 'host' | 'worktree' | 'docker';
|
|
19
19
|
timeoutMs?: number;
|
|
20
|
+
internal?: boolean;
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
export interface Workflow {
|
|
@@ -38,6 +39,7 @@ export interface WorkflowNodeRun {
|
|
|
38
39
|
permissionMode?: string;
|
|
39
40
|
timeoutMs?: number;
|
|
40
41
|
stage?: string;
|
|
42
|
+
internal?: boolean;
|
|
41
43
|
status: WorkflowNodeStatus;
|
|
42
44
|
a2aTaskId?: string;
|
|
43
45
|
startedAt?: number;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import express from 'express';
|
|
2
|
+
|
|
3
|
+
import { collectDiagnostics } from '../services/diagnostics.js';
|
|
4
|
+
|
|
5
|
+
const router = express.Router();
|
|
6
|
+
|
|
7
|
+
router.get('/', (req, res) => {
|
|
8
|
+
res.json(collectDiagnostics({
|
|
9
|
+
installMode: req.app.locals.installMode,
|
|
10
|
+
serverVersion: req.app.locals.serverVersion,
|
|
11
|
+
wss: req.app.locals.wss,
|
|
12
|
+
}));
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
export default router;
|