@pixelbyte-software/pixcode 1.36.3 → 1.37.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-CfHK8y_H.css +32 -0
- package/dist/assets/{index-Bp8mXdQd.js → index-D8uNxHf1.js} +165 -159
- package/dist/index.html +2 -2
- package/dist-server/server/daemon-manager.js +18 -12
- package/dist-server/server/daemon-manager.js.map +1 -1
- package/dist-server/server/database/db.js +53 -2
- package/dist-server/server/database/db.js.map +1 -1
- package/dist-server/server/index.js +11 -4
- 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 +143 -26
- package/dist-server/server/modules/orchestration/workflows/workflow-runner.js.map +1 -1
- package/dist-server/server/routes/taskmaster.js +194 -0
- package/dist-server/server/routes/taskmaster.js.map +1 -1
- package/dist-server/server/routes/telegram.js +16 -2
- package/dist-server/server/routes/telegram.js.map +1 -1
- 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/bot.js +48 -6
- package/dist-server/server/services/telegram/bot.js.map +1 -1
- package/dist-server/server/services/telegram/control-center.js +903 -0
- package/dist-server/server/services/telegram/control-center.js.map +1 -0
- package/dist-server/server/services/telegram/telegram-http-client.js +26 -4
- package/dist-server/server/services/telegram/telegram-http-client.js.map +1 -1
- package/dist-server/server/services/telegram/translations.js +150 -2
- package/dist-server/server/services/telegram/translations.js.map +1 -1
- package/package.json +3 -1
- package/scripts/smoke/chat-realtime-hydration.mjs +44 -0
- package/scripts/smoke/daemon-entrypoint.mjs +20 -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/orchestration-user-facing-output.mjs +25 -0
- package/scripts/smoke/shell-manual-disconnect.mjs +30 -0
- package/scripts/smoke/side-panel-editor-layout.mjs +34 -0
- package/scripts/smoke/static-root-routing.mjs +21 -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/telegram-control.mjs +242 -0
- package/scripts/smoke/update-issue-progress.mjs +69 -0
- package/scripts/smoke/version-modal-autoshow.mjs +29 -0
- package/server/daemon-manager.js +17 -12
- package/server/database/db.js +56 -2
- package/server/index.js +12 -5
- 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 +149 -26
- package/server/modules/orchestration/workflows/workflow.types.ts +2 -0
- package/server/routes/taskmaster.js +201 -0
- package/server/routes/telegram.js +17 -2
- package/server/services/install-jobs.js +1 -0
- package/server/services/notification-orchestrator.js +76 -8
- package/server/services/telegram/bot.js +58 -6
- package/server/services/telegram/control-center.js +965 -0
- package/server/services/telegram/telegram-http-client.js +25 -4
- package/server/services/telegram/translations.js +150 -2
- package/dist/assets/index-Dx7QyTSN.css +0 -32
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import assert from 'node:assert/strict';
|
|
4
|
+
import { readFileSync } from 'node:fs';
|
|
5
|
+
|
|
6
|
+
const source = readFileSync('server/modules/orchestration/workflows/workflow-runner.ts', 'utf8');
|
|
7
|
+
|
|
8
|
+
assert.ok(
|
|
9
|
+
source.includes('userFacingTaskText'),
|
|
10
|
+
'Workflow runner should normalize A2A task output into user-facing text.',
|
|
11
|
+
);
|
|
12
|
+
assert.ok(
|
|
13
|
+
!source.includes('`${message.role}: ${message.text}`'),
|
|
14
|
+
'Workflow runner must not prefix final/user-facing output with raw A2A roles like agent:.',
|
|
15
|
+
);
|
|
16
|
+
assert.ok(
|
|
17
|
+
source.includes('Do not expose internal prompts, memory lookup, skill/tool instructions, raw agent logs, or role prefixes like "agent:" and "user:".'),
|
|
18
|
+
'Agent-team final report prompt should explicitly block internal process leakage.',
|
|
19
|
+
);
|
|
20
|
+
assert.ok(
|
|
21
|
+
source.includes('Do not mention internal instructions, memory files, skill use, or tool protocol unless the user explicitly asks.'),
|
|
22
|
+
'Agent-team worker prompts should discourage internal process leakage.',
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
console.log('orchestration user-facing output smoke passed');
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import assert from 'node:assert/strict';
|
|
4
|
+
import { readFileSync } from 'node:fs';
|
|
5
|
+
|
|
6
|
+
const connectionSource = readFileSync('src/components/shell/hooks/useShellConnection.ts', 'utf8');
|
|
7
|
+
const runtimeSource = readFileSync('src/components/shell/hooks/useShellRuntime.ts', 'utf8');
|
|
8
|
+
|
|
9
|
+
assert.ok(
|
|
10
|
+
connectionSource.includes('manualDisconnectRef'),
|
|
11
|
+
'Shell connection should track manual disconnects separately from socket closes.',
|
|
12
|
+
);
|
|
13
|
+
assert.ok(
|
|
14
|
+
connectionSource.includes('manualDisconnectRef.current = true'),
|
|
15
|
+
'Manual disconnect should disable auto-reconnect.',
|
|
16
|
+
);
|
|
17
|
+
assert.ok(
|
|
18
|
+
connectionSource.includes('manualDisconnectRef.current = false'),
|
|
19
|
+
'Explicit connect should re-enable connection attempts.',
|
|
20
|
+
);
|
|
21
|
+
assert.ok(
|
|
22
|
+
connectionSource.includes('manualDisconnectRef.current'),
|
|
23
|
+
'Auto-connect effect should check the manual disconnect guard.',
|
|
24
|
+
);
|
|
25
|
+
assert.ok(
|
|
26
|
+
runtimeSource.includes('disconnectFromShell(false)'),
|
|
27
|
+
'Internal project/session/restart disconnects should not set the manual disconnect guard.',
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
console.log('shell manual disconnect smoke passed');
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import assert from 'node:assert/strict';
|
|
4
|
+
import { readFileSync } from 'node:fs';
|
|
5
|
+
|
|
6
|
+
const mainSource = readFileSync('src/components/main-content/view/MainContent.tsx', 'utf8');
|
|
7
|
+
const editorSource = readFileSync('src/components/code-editor/view/EditorSidebar.tsx', 'utf8');
|
|
8
|
+
|
|
9
|
+
assert.ok(
|
|
10
|
+
mainSource.includes('SIDE_PANEL_MAX_WIDTH = 50'),
|
|
11
|
+
'Side panel split width should be capped at half of the main surface.',
|
|
12
|
+
);
|
|
13
|
+
assert.ok(
|
|
14
|
+
mainSource.includes('dockEditorInsideFilesPanel'),
|
|
15
|
+
'Files tab should dock the editor inside the files side panel.',
|
|
16
|
+
);
|
|
17
|
+
assert.ok(
|
|
18
|
+
mainSource.includes('showSidePanelSplit)'),
|
|
19
|
+
'Side panel split should remain active even when a file editor is open.',
|
|
20
|
+
);
|
|
21
|
+
assert.ok(
|
|
22
|
+
mainSource.includes('!dockEditorInsideFilesPanel &&'),
|
|
23
|
+
'Global editor sidebar should not render when the editor is docked inside the files panel.',
|
|
24
|
+
);
|
|
25
|
+
assert.ok(
|
|
26
|
+
mainSource.includes('max-w-[50%]'),
|
|
27
|
+
'The file list column inside the files panel should be capped at 50%.',
|
|
28
|
+
);
|
|
29
|
+
assert.ok(
|
|
30
|
+
editorSource.includes("useFlexLayout ? 'flex-1' : ''"),
|
|
31
|
+
'EditorSidebar should fill inline docked space when fillSpace is enabled.',
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
console.log('side panel editor layout smoke passed');
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import assert from 'node:assert/strict';
|
|
4
|
+
import { readFileSync } from 'node:fs';
|
|
5
|
+
|
|
6
|
+
const source = readFileSync('server/index.js', 'utf8');
|
|
7
|
+
const distStatic = "app.use(express.static(path.join(APP_ROOT, 'dist')";
|
|
8
|
+
const publicStatic = "app.use(express.static(path.join(APP_ROOT, 'public')";
|
|
9
|
+
|
|
10
|
+
const distIndex = source.indexOf(distStatic);
|
|
11
|
+
const publicIndex = source.indexOf(publicStatic);
|
|
12
|
+
|
|
13
|
+
assert.ok(distIndex !== -1, 'dist static middleware is missing');
|
|
14
|
+
assert.ok(publicIndex !== -1, 'public static middleware is missing');
|
|
15
|
+
assert.ok(distIndex < publicIndex, 'dist static middleware must run before public');
|
|
16
|
+
assert.ok(
|
|
17
|
+
source.slice(publicIndex, publicIndex + 160).includes('index: false'),
|
|
18
|
+
'public static middleware must not serve public/index.html for /',
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
console.log('static root routing smoke passed');
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import assert from 'node:assert/strict';
|
|
4
|
+
import { readFileSync } from 'node:fs';
|
|
5
|
+
|
|
6
|
+
const source = readFileSync('server/modules/orchestration/workflows/workflow-runner.ts', 'utf8');
|
|
7
|
+
const types = readFileSync('server/modules/orchestration/workflows/workflow.types.ts', 'utf8');
|
|
8
|
+
const panel = readFileSync('src/components/orchestration/workflows/WorkflowRunPanel.tsx', 'utf8');
|
|
9
|
+
|
|
10
|
+
assert.ok(
|
|
11
|
+
types.includes('internal?: boolean'),
|
|
12
|
+
'Workflow nodes/runs should carry an internal flag so init/compact packets stay out of user-facing output.',
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
assert.ok(
|
|
16
|
+
source.includes('handoffInitPrompt('),
|
|
17
|
+
'Strict handoff should create an internal init prompt before each agent work step.',
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
assert.ok(
|
|
21
|
+
source.includes('handoffCompactPrompt('),
|
|
22
|
+
'Strict handoff should create an internal compact prompt after each agent work step.',
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
assert.ok(
|
|
26
|
+
source.includes("safeAgentNodeId(agent, index, 'init')"),
|
|
27
|
+
'Sequential handoff should create a per-agent init node.',
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
assert.ok(
|
|
31
|
+
source.includes("safeAgentNodeId(agent, index, 'work')"),
|
|
32
|
+
'Sequential handoff should create a per-agent work node.',
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
assert.ok(
|
|
36
|
+
source.includes("safeAgentNodeId(agent, index, 'compact')"),
|
|
37
|
+
'Sequential handoff should create a per-agent compact node.',
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
assert.ok(
|
|
41
|
+
/inputs:\s*index === 0\s*\?\s*\[\]\s*:\s*\[safeAgentNodeId\(agents\[index - 1\], index - 1, 'compact'\)\]/m.test(source),
|
|
42
|
+
'Each init node after the first must depend on the previous agent compact node.',
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
assert.ok(
|
|
46
|
+
/inputs:\s*\[initNodeId\]/m.test(source),
|
|
47
|
+
'Each work node must depend on its init node.',
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
assert.ok(
|
|
51
|
+
/inputs:\s*\[workNodeId\]/m.test(source),
|
|
52
|
+
'Each compact node must depend on its work node.',
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
assert.ok(
|
|
56
|
+
panel.includes('visibleNodeRuns'),
|
|
57
|
+
'Workflow UI should filter internal init/compact node runs from visible output.',
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
console.log('strict handoff compact smoke passed');
|
|
@@ -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 taskmasterRoutes = readFileSync('server/routes/taskmaster.js', 'utf8');
|
|
7
|
+
const taskService = readFileSync('server/modules/orchestration/tasks/orchestration-task.service.ts', 'utf8');
|
|
8
|
+
const taskTypes = readFileSync('server/modules/orchestration/tasks/orchestration-task.types.ts', 'utf8');
|
|
9
|
+
const telegramControl = readFileSync('server/services/telegram/control-center.js', 'utf8');
|
|
10
|
+
const translations = readFileSync('server/services/telegram/translations.js', 'utf8');
|
|
11
|
+
|
|
12
|
+
assert.ok(
|
|
13
|
+
taskmasterRoutes.includes("router.post('/execute/:projectName/:taskId'"),
|
|
14
|
+
'TaskMaster route should expose project/task execution endpoint.',
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
assert.ok(
|
|
18
|
+
taskmasterRoutes.includes('orchestrationTaskService.dispatch') && taskmasterRoutes.includes('model'),
|
|
19
|
+
'TaskMaster execution should dispatch through orchestration with model support.',
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
assert.ok(
|
|
23
|
+
taskService.includes('projectPath?: string') || taskTypes.includes('projectPath?: string'),
|
|
24
|
+
'Orchestration task dispatch should carry projectPath into A2A workspace metadata.',
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
assert.ok(
|
|
28
|
+
taskService.includes('model: input.model') && taskService.includes('projectPath: input.projectPath'),
|
|
29
|
+
'Task dispatch should forward selected model and project path to A2A.',
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
assert.ok(
|
|
33
|
+
telegramControl.includes("'/tasks'") && telegramControl.includes("'/task'"),
|
|
34
|
+
'Telegram control should register TaskMaster commands.',
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
assert.ok(
|
|
38
|
+
telegramControl.includes('showTaskMasterTasks') && telegramControl.includes('runTaskMasterTask'),
|
|
39
|
+
'Telegram control should list and execute TaskMaster tasks.',
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
assert.ok(
|
|
43
|
+
telegramControl.includes('/api/taskmaster/execute/') && telegramControl.includes('monitorA2ATask'),
|
|
44
|
+
'Telegram TaskMaster execution should call the backend and monitor the A2A task.',
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
assert.ok(
|
|
48
|
+
translations.includes('control.button.tasks') && translations.includes('control.taskStarted'),
|
|
49
|
+
'Telegram translations should include TaskMaster task controls.',
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
console.log('taskmaster execution telegram smoke passed');
|
|
@@ -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,242 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import assert from 'node:assert/strict';
|
|
4
|
+
import { mkdirSync, readFileSync, rmSync } from 'node:fs';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
|
|
7
|
+
const runtimeDir = path.resolve('.pixcode-dev', 'smoke-telegram-control');
|
|
8
|
+
mkdirSync(runtimeDir, { recursive: true });
|
|
9
|
+
process.env.DATABASE_PATH = path.join(runtimeDir, 'auth.db');
|
|
10
|
+
|
|
11
|
+
const checks = [
|
|
12
|
+
{
|
|
13
|
+
name: 'telegram bot wires the remote control center and callback queries',
|
|
14
|
+
file: 'server/services/telegram/bot.js',
|
|
15
|
+
test: (source) => (
|
|
16
|
+
source.includes('handleTelegramControlMessage')
|
|
17
|
+
&& source.includes('handleTelegramControlCallback')
|
|
18
|
+
&& source.includes("bot.on('callback_query'")
|
|
19
|
+
),
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
name: 'telegram HTTP client polls callback queries and can answer them',
|
|
23
|
+
file: 'server/services/telegram/telegram-http-client.js',
|
|
24
|
+
test: (source) => (
|
|
25
|
+
source.includes("allowed_updates: ['message', 'callback_query']")
|
|
26
|
+
&& source.includes('answerCallbackQuery')
|
|
27
|
+
&& source.includes('editMessageText')
|
|
28
|
+
&& source.includes("this.emit('callback_query'")
|
|
29
|
+
),
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
name: 'telegram control center exposes provider, model, workflow, install, and settings actions',
|
|
33
|
+
file: 'server/services/telegram/control-center.js',
|
|
34
|
+
test: (source) => (
|
|
35
|
+
source.includes('showMainMenu')
|
|
36
|
+
&& source.includes('showProviderMenu')
|
|
37
|
+
&& source.includes('showModelMenu')
|
|
38
|
+
&& source.includes('showWorkflowMenu')
|
|
39
|
+
&& source.includes('runWorkflow')
|
|
40
|
+
&& source.includes('startCliInstall')
|
|
41
|
+
&& source.includes('updateTelegramControlState')
|
|
42
|
+
&& source.includes('/api/agent')
|
|
43
|
+
&& source.includes('/api/orchestration/workflows')
|
|
44
|
+
),
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
name: 'telegram link state persists remote-control preferences',
|
|
48
|
+
file: 'server/database/db.js',
|
|
49
|
+
test: (source) => (
|
|
50
|
+
source.includes('telegram_control')
|
|
51
|
+
&& source.includes('getControlState')
|
|
52
|
+
&& source.includes('updateControlState')
|
|
53
|
+
&& source.includes('remoteControlEnabled')
|
|
54
|
+
),
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
name: 'telegram settings UI exposes remote-control toggles',
|
|
58
|
+
file: 'src/components/settings/view/tabs/telegram-settings/TelegramSettingsTab.tsx',
|
|
59
|
+
test: (source) => (
|
|
60
|
+
source.includes('controlEnabled')
|
|
61
|
+
&& source.includes('progressMode')
|
|
62
|
+
&& source.includes('telegram.control.title')
|
|
63
|
+
&& source.includes('telegram.control.progressMode')
|
|
64
|
+
),
|
|
65
|
+
},
|
|
66
|
+
];
|
|
67
|
+
|
|
68
|
+
const failures = [];
|
|
69
|
+
|
|
70
|
+
for (const check of checks) {
|
|
71
|
+
let source = '';
|
|
72
|
+
try {
|
|
73
|
+
source = readFileSync(check.file, 'utf8');
|
|
74
|
+
} catch {
|
|
75
|
+
failures.push(`${check.name} (${check.file} missing)`);
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (!check.test(source)) failures.push(check.name);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
try {
|
|
83
|
+
const {
|
|
84
|
+
handleTelegramControlCallback,
|
|
85
|
+
handleTelegramControlMessage,
|
|
86
|
+
} = await import('../../server/services/telegram/control-center.js');
|
|
87
|
+
const {
|
|
88
|
+
handleIncomingTelegramMessage,
|
|
89
|
+
setTelegramBotForTesting,
|
|
90
|
+
} = await import('../../server/services/telegram/bot.js');
|
|
91
|
+
const { telegramLinksDb } = await import('../../server/database/db.js');
|
|
92
|
+
|
|
93
|
+
const userId = 4242;
|
|
94
|
+
telegramLinksDb.unlink(userId);
|
|
95
|
+
telegramLinksDb.setPairingCode(userId, '123456', new Date(Date.now() + 600_000).toISOString(), 'tr');
|
|
96
|
+
telegramLinksDb.verify(userId, 'chat-4242', 'ali');
|
|
97
|
+
|
|
98
|
+
const sent = [];
|
|
99
|
+
const bot = {
|
|
100
|
+
sendMessage: async (chatId, text, extra = {}) => {
|
|
101
|
+
sent.push({ chatId, text, extra });
|
|
102
|
+
return { ok: true };
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const link = telegramLinksDb.getByUserId(userId);
|
|
107
|
+
const expectReply = async (input, expectedFragment) => {
|
|
108
|
+
sent.length = 0;
|
|
109
|
+
const handled = await handleTelegramControlMessage({
|
|
110
|
+
bot,
|
|
111
|
+
msg: { chat: { id: 4242 }, text: input },
|
|
112
|
+
link,
|
|
113
|
+
});
|
|
114
|
+
assert.equal(handled, true, `${input} should be handled`);
|
|
115
|
+
assert.ok(sent.length > 0, `${input} should send at least one reply`);
|
|
116
|
+
assert.ok(
|
|
117
|
+
sent[0].text.includes(expectedFragment),
|
|
118
|
+
`${input} should include "${expectedFragment}" but got "${sent[0].text}"`,
|
|
119
|
+
);
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
await expectReply('/start', 'Pixcode Telegram kontrol merkezi');
|
|
123
|
+
await expectReply('/help', 'Komutlar:');
|
|
124
|
+
await expectReply('/start@Otobot', 'Pixcode Telegram kontrol merkezi');
|
|
125
|
+
await expectReply('/help@Otobot', 'Komutlar:');
|
|
126
|
+
await expectReply('/', 'Komutlar:');
|
|
127
|
+
|
|
128
|
+
const menuEvents = [];
|
|
129
|
+
const menuBot = {
|
|
130
|
+
sendMessage: async (chatId, text, extra = {}) => {
|
|
131
|
+
menuEvents.push({ type: 'send', chatId, text, extra });
|
|
132
|
+
return { ok: true, message_id: 77 };
|
|
133
|
+
},
|
|
134
|
+
editMessageText: async (text, extra = {}) => {
|
|
135
|
+
menuEvents.push({ type: 'edit', text, extra });
|
|
136
|
+
return { ok: true, message_id: extra.message_id };
|
|
137
|
+
},
|
|
138
|
+
answerCallbackQuery: async () => ({ ok: true }),
|
|
139
|
+
};
|
|
140
|
+
const findButton = (markup, predicate) => {
|
|
141
|
+
for (const row of markup?.inline_keyboard || []) {
|
|
142
|
+
for (const candidate of row) {
|
|
143
|
+
if (predicate(candidate)) return candidate;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return null;
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
menuEvents.length = 0;
|
|
150
|
+
await handleTelegramControlMessage({
|
|
151
|
+
bot: menuBot,
|
|
152
|
+
msg: { chat: { id: 4242 }, text: '/settings' },
|
|
153
|
+
link,
|
|
154
|
+
});
|
|
155
|
+
const settingsMenu = menuEvents.at(-1);
|
|
156
|
+
const languageButton = findButton(
|
|
157
|
+
settingsMenu.extra.reply_markup,
|
|
158
|
+
(candidate) => /language|dil/i.test(candidate.text),
|
|
159
|
+
);
|
|
160
|
+
assert.ok(languageButton, 'settings menu should expose a language button');
|
|
161
|
+
|
|
162
|
+
menuEvents.length = 0;
|
|
163
|
+
await handleTelegramControlCallback({
|
|
164
|
+
bot: menuBot,
|
|
165
|
+
query: {
|
|
166
|
+
id: 'query-language-menu',
|
|
167
|
+
data: languageButton.callback_data,
|
|
168
|
+
message: { chat: { id: 4242 }, message_id: 77 },
|
|
169
|
+
},
|
|
170
|
+
link,
|
|
171
|
+
});
|
|
172
|
+
assert.equal(menuEvents.length, 1, 'callback menus should replace the existing menu message');
|
|
173
|
+
assert.equal(menuEvents[0].type, 'edit', 'callback menus should use editMessageText');
|
|
174
|
+
const trButton = findButton(menuEvents[0].extra.reply_markup, (candidate) => candidate.text === 'tr');
|
|
175
|
+
assert.ok(trButton, 'language menu should include Turkish');
|
|
176
|
+
|
|
177
|
+
menuEvents.length = 0;
|
|
178
|
+
await handleTelegramControlCallback({
|
|
179
|
+
bot: menuBot,
|
|
180
|
+
query: {
|
|
181
|
+
id: 'query-language-tr',
|
|
182
|
+
data: trButton.callback_data,
|
|
183
|
+
message: { chat: { id: 4242 }, message_id: 77 },
|
|
184
|
+
},
|
|
185
|
+
link,
|
|
186
|
+
});
|
|
187
|
+
assert.equal(telegramLinksDb.getByUserId(userId).language, 'tr', 'language selection should persist');
|
|
188
|
+
assert.equal(menuEvents.length, 1, 'language selection should not send a confirmation plus a second menu');
|
|
189
|
+
assert.equal(menuEvents[0].type, 'edit', 'language selection should replace the menu in place');
|
|
190
|
+
assert.ok(
|
|
191
|
+
menuEvents[0].text.includes('Pixcode Telegram kontrol merkezi'),
|
|
192
|
+
`Turkish menu should render after selection, got "${menuEvents[0].text}"`,
|
|
193
|
+
);
|
|
194
|
+
assert.ok(
|
|
195
|
+
!menuEvents[0].text.includes('Project:'),
|
|
196
|
+
'Turkish menu should not keep English summary labels after language selection',
|
|
197
|
+
);
|
|
198
|
+
|
|
199
|
+
const botLevelMessages = [];
|
|
200
|
+
setTelegramBotForTesting({
|
|
201
|
+
sendMessage: async (chatId, text, extra = {}) => {
|
|
202
|
+
botLevelMessages.push({ chatId, text, extra });
|
|
203
|
+
return { ok: true };
|
|
204
|
+
},
|
|
205
|
+
answerCallbackQuery: async () => ({ ok: true }),
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
const expectBotReply = async (input, expectedFragment) => {
|
|
209
|
+
botLevelMessages.length = 0;
|
|
210
|
+
await handleIncomingTelegramMessage({
|
|
211
|
+
chat: { id: 'chat-4242' },
|
|
212
|
+
text: input,
|
|
213
|
+
message_id: 1,
|
|
214
|
+
from: { username: 'ali' },
|
|
215
|
+
});
|
|
216
|
+
assert.ok(botLevelMessages.length > 0, `${input} should reply from bot.js`);
|
|
217
|
+
assert.ok(
|
|
218
|
+
botLevelMessages[0].text.includes(expectedFragment),
|
|
219
|
+
`${input} should include "${expectedFragment}" but got "${botLevelMessages[0].text}"`,
|
|
220
|
+
);
|
|
221
|
+
assert.ok(
|
|
222
|
+
!botLevelMessages.some((entry) => entry.text.includes('Mesaj son oturumuna iletildi')),
|
|
223
|
+
`${input} should never hit the bridge queue reply`,
|
|
224
|
+
);
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
await expectBotReply('/start', 'Nasıl başlarsın');
|
|
228
|
+
await expectBotReply('/help', 'Komutlar:');
|
|
229
|
+
|
|
230
|
+
telegramLinksDb.unlink(userId);
|
|
231
|
+
} catch (error) {
|
|
232
|
+
failures.push(error?.message || String(error));
|
|
233
|
+
} finally {
|
|
234
|
+
rmSync(runtimeDir, { recursive: true, force: true });
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (failures.length > 0) {
|
|
238
|
+
console.error(`Telegram control smoke failed:\n- ${failures.join('\n- ')}`);
|
|
239
|
+
process.exit(1);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
console.log('telegram control 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,29 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import assert from 'node:assert/strict';
|
|
4
|
+
import { readFileSync } from 'node:fs';
|
|
5
|
+
|
|
6
|
+
const source = readFileSync('src/components/sidebar/view/Sidebar.tsx', 'utf8');
|
|
7
|
+
|
|
8
|
+
assert.ok(
|
|
9
|
+
source.includes('VERSION_RELEASE_NOTES_SEEN_KEY'),
|
|
10
|
+
'Sidebar should persist the latest equal-version release notes it auto-showed.',
|
|
11
|
+
);
|
|
12
|
+
assert.ok(
|
|
13
|
+
source.includes('localStorage.getItem(VERSION_RELEASE_NOTES_SEEN_KEY)'),
|
|
14
|
+
'Sidebar should read the seen release-notes version from localStorage.',
|
|
15
|
+
);
|
|
16
|
+
assert.ok(
|
|
17
|
+
source.includes('localStorage.setItem(VERSION_RELEASE_NOTES_SEEN_KEY, latestVersion)'),
|
|
18
|
+
'Sidebar should mark equal-version release notes as seen when auto-showing them.',
|
|
19
|
+
);
|
|
20
|
+
assert.ok(
|
|
21
|
+
source.includes('hasSeenCurrentReleaseNotes'),
|
|
22
|
+
'Sidebar should avoid auto-showing release notes when the current version was already seen.',
|
|
23
|
+
);
|
|
24
|
+
assert.ok(
|
|
25
|
+
source.includes('!hasSeenCurrentReleaseNotes'),
|
|
26
|
+
'The auto-show condition should be gated by the seen-version check.',
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
console.log('version modal autoshow smoke passed');
|
package/server/daemon-manager.js
CHANGED
|
@@ -95,15 +95,25 @@ function quoteSystemdArg(arg) {
|
|
|
95
95
|
return `"${String(arg).replace(/(["\\$`])/g, '\\$1')}"`;
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
-
function resolveDaemonCliEntryPath(context = {}) {
|
|
98
|
+
export function resolveDaemonCliEntryPath(context = {}) {
|
|
99
99
|
const appRoot = context.appRoot || process.cwd();
|
|
100
100
|
const explicitCliEntry = context.cliEntry ? path.resolve(context.cliEntry) : null;
|
|
101
101
|
const argvCliEntry = process.argv[1] ? path.resolve(process.argv[1]) : null;
|
|
102
|
+
const distCliEntry = path.join(appRoot, 'dist-server', 'server', 'cli.js');
|
|
103
|
+
const sourceCliEntry = path.join(appRoot, 'server', 'cli.js');
|
|
104
|
+
const normalizeCliCandidate = (candidate) => {
|
|
105
|
+
if (!candidate) return null;
|
|
106
|
+
const resolved = path.resolve(candidate);
|
|
107
|
+
if (resolved === sourceCliEntry && fs.existsSync(distCliEntry)) {
|
|
108
|
+
return distCliEntry;
|
|
109
|
+
}
|
|
110
|
+
return resolved;
|
|
111
|
+
};
|
|
102
112
|
const candidatePaths = [
|
|
103
|
-
explicitCliEntry,
|
|
104
|
-
argvCliEntry,
|
|
105
|
-
|
|
106
|
-
|
|
113
|
+
normalizeCliCandidate(explicitCliEntry),
|
|
114
|
+
normalizeCliCandidate(argvCliEntry),
|
|
115
|
+
distCliEntry,
|
|
116
|
+
sourceCliEntry,
|
|
107
117
|
].filter(Boolean);
|
|
108
118
|
|
|
109
119
|
const existingPath = candidatePaths.find(candidate => fs.existsSync(candidate));
|
|
@@ -111,7 +121,7 @@ function resolveDaemonCliEntryPath(context = {}) {
|
|
|
111
121
|
return existingPath;
|
|
112
122
|
}
|
|
113
123
|
|
|
114
|
-
return explicitCliEntry || argvCliEntry ||
|
|
124
|
+
return normalizeCliCandidate(explicitCliEntry) || normalizeCliCandidate(argvCliEntry) || distCliEntry;
|
|
115
125
|
}
|
|
116
126
|
|
|
117
127
|
function hasPixcodeBinary() {
|
|
@@ -210,12 +220,7 @@ function parseDaemonArgs(args) {
|
|
|
210
220
|
|
|
211
221
|
function buildDaemonExecStart({ appRoot, serverPort, databasePath, nodeExecPath, cliEntry }) {
|
|
212
222
|
const nodeExec = nodeExecPath || process.execPath || 'node';
|
|
213
|
-
const
|
|
214
|
-
? path.resolve(cliEntry)
|
|
215
|
-
: (process.argv[1] ? path.resolve(process.argv[1]) : path.join(appRoot, 'dist-server', 'server', 'cli.js'));
|
|
216
|
-
const resolvedCliEntry = fs.existsSync(cliCandidate)
|
|
217
|
-
? cliCandidate
|
|
218
|
-
: path.join(appRoot, 'dist-server', 'server', 'cli.js');
|
|
223
|
+
const resolvedCliEntry = resolveDaemonCliEntryPath({ appRoot, cliEntry });
|
|
219
224
|
|
|
220
225
|
const args = [nodeExec, resolvedCliEntry, 'start', '--port', String(serverPort)];
|
|
221
226
|
if (databasePath) {
|