Merge remote-tracking branch 'origin/master' into editor-agent-refactored

# Conflicts:
#	docs/README.md
#	src/components/image-editor/EditorAgentConversation/EditorAgentConversationPanelView.tsx
#	src/components/image-editor/EditorAgentConversation/useEditorAgentConversation.ts
#	src/components/image-editor/EditorAgentConversationPanelView.test.tsx
#	src/components/image-editor/ImageCanvasStageView.tsx
This commit is contained in:
2026-07-17 17:32:08 +08:00
272 changed files with 1697 additions and 1300 deletions
+6 -61
View File
@@ -1385,47 +1385,7 @@ const desktopCapabilityFlowContracts = [
],
},
];
const h5NativeAppRouteFlowContracts = [
{
route: '/child-motion-demo',
label: 'child motion demo',
files: [
'src/components/platform-entry/PlatformEntryFlowShellImpl.tsx',
'src/routing/appRoutes.tsx',
'src/routing/appRoutes.test.ts',
'src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx',
],
snippets: [
[
'src/components/platform-entry/PlatformEntryFlowShellImpl.tsx',
"navigateHostNativePage('/child-motion-demo')",
],
[
'src/components/platform-entry/PlatformEntryFlowShellImpl.tsx',
"window.location.assign('/child-motion-demo')",
],
['src/routing/appRoutes.tsx', "normalizedPath === '/child-motion-demo'"],
['src/routing/appRoutes.test.ts', "matchAppRoute('/child-motion-demo')"],
[
'src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx',
"request.method === 'navigation.openNativePage'",
],
[
'src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx',
"url === '/child-motion-demo'",
],
],
tests: [
'src/routing/appRoutes.test.ts',
],
targetedTests: [
{
filePath: 'src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx',
name: 'native app opens child motion demo through host navigation bridge',
},
],
},
];
const h5NativeAppRouteFlowContracts = [];
const expectedWechatHostBridgeFiles = [
'dispatch.js',
'payment.js',
@@ -1732,8 +1692,6 @@ const h5HostBridgeTests = [
'src/services/clipboard.test.ts',
'src/services/appTitle.test.ts',
];
const h5PlatformHostBridgeIntegrationTest =
'native app jump hop draft completion sends host notification and badge from platform shell';
const h5NativeAppRouteFlowTestSteps = h5NativeAppRouteFlowContracts.flatMap(
(contract) =>
(contract.targetedTests ?? []).map((test) => ({
@@ -1764,18 +1722,6 @@ const steps = [
command: npmCommand,
args: ['run', 'test', '--', ...h5HostBridgeTests],
},
{
label: 'h5-platform-host-bridge-integration',
command: npmCommand,
args: [
'run',
'test',
'--',
'src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx',
'-t',
h5PlatformHostBridgeIntegrationTest,
],
},
...h5NativeAppRouteFlowTestSteps,
{
label: 'wechat-shell-tests',
@@ -1807,16 +1753,16 @@ const steps = [
command: npmCommand,
args: ['run', 'mobile-shell:export'],
},
{
label: 'desktop-shell-typecheck',
command: npmCommand,
args: ['run', 'desktop-shell:typecheck'],
},
{
label: 'desktop-shell-test',
command: npmCommand,
args: ['run', 'desktop-shell:test'],
},
{
label: 'desktop-shell-typecheck',
command: npmCommand,
args: ['run', 'desktop-shell:typecheck'],
},
{
label: 'desktop-shell-release-build-smoke',
command: npmCommand,
@@ -2439,7 +2385,6 @@ function assertH5HostBridgePayloadBoundaries() {
'HOST_BRIDGE_RUNTIME_REFRESH_TIMEOUT_MS',
'HOST_BRIDGE_USER_INTERACTION_TIMEOUT_MS',
'HOST_BRIDGE_SCANNER_TIMEOUT_MS',
'HOST_BRIDGE_APP_TITLE_MAX_LENGTH',
]) {
if (!h5HostBridgeSource.includes(`${sharedBoundary},`)) {
throw new Error(
@@ -1,18 +1,18 @@
#!/usr/bin/env node
import { spawnSync } from 'node:child_process';
import { createHash } from 'node:crypto';
import {
chmodSync,
copyFileSync,
existsSync,
symlinkSync,
mkdirSync,
mkdtempSync,
readFileSync,
rmSync,
symlinkSync,
writeFileSync,
} from 'node:fs';
import { createHash } from 'node:crypto';
import { tmpdir } from 'node:os';
import path from 'node:path';
@@ -3,7 +3,6 @@
import { spawnSync } from 'node:child_process';
import { createHash } from 'node:crypto';
import {
existsSync,
mkdirSync,
mkdtempSync,
readFileSync,
+1 -1
View File
@@ -3,8 +3,8 @@
import { spawnSync } from 'node:child_process';
import {
existsSync,
mkdtempSync,
mkdirSync,
mkdtempSync,
readFileSync,
rmSync,
symlinkSync,
+3 -3
View File
@@ -1,12 +1,12 @@
#!/usr/bin/env node
import { createHash, randomBytes } from 'node:crypto';
import { readFile } from 'node:fs/promises';
import http from 'node:http';
import https from 'node:https';
import net from 'node:net';
import tls from 'node:tls';
import { createHash, randomBytes } from 'node:crypto';
import { readFile } from 'node:fs/promises';
import nodePath from 'node:path';
import tls from 'node:tls';
const DEFAULT_TIMEOUT_MS = 5000;
const DEFAULT_SPACETIME_DATABASE = 'genarrative-prod';
+1 -1
View File
@@ -1,8 +1,8 @@
#!/usr/bin/env node
import { spawnSync } from 'node:child_process';
import { accessSync, constants, readFileSync } from 'node:fs';
import net from 'node:net';
import { spawnSync } from 'node:child_process';
import { userInfo } from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
@@ -3,7 +3,6 @@
import { spawnSync } from 'node:child_process';
import {
chmodSync,
existsSync,
mkdtempSync,
readFileSync,
rmSync,
@@ -7,8 +7,8 @@ import {
existsSync,
mkdirSync,
mkdtempSync,
realpathSync,
readFileSync,
realpathSync,
rmSync,
statSync,
symlinkSync,
@@ -4,8 +4,8 @@ import { spawnSync } from 'node:child_process';
import {
chmodSync,
existsSync,
mkdtempSync,
mkdirSync,
mkdtempSync,
readFileSync,
rmSync,
writeFileSync,
@@ -13,11 +13,56 @@ const checks = [
includes: 'npm run check:rustfmt',
reason: 'API 生产构建必须在编译前执行 Rust 格式检查。',
},
{
file: 'jenkins/Jenkinsfile.production-api-build',
includes: 'npm run check:server-rs-ddd',
reason: 'API 生产构建必须执行 server-rs DDD/schema/runtime-access 门禁。',
},
{
file: 'jenkins/Jenkinsfile.production-api-build',
includes: 'cargo check -p api-server --all-targets --manifest-path server-rs/Cargo.toml',
reason: 'API 生产构建必须执行 api-server 全 target 编译检查。',
},
{
file: 'jenkins/Jenkinsfile.production-stdb-module-build',
includes: 'npm run check:rustfmt',
reason: 'Stdb module 生产构建必须在编译前执行 Rust 格式检查。',
},
{
file: 'jenkins/Jenkinsfile.production-stdb-module-build',
includes: 'npm run check:server-rs-ddd',
reason: 'Stdb module 生产构建必须执行 DDD/schema/runtime-access 门禁。',
},
{
file: 'jenkins/Jenkinsfile.production-stdb-module-build',
includes: 'npm run check:admin-account-procedures',
reason: 'Stdb module 生产构建必须执行管理员 procedure smoke。',
},
{
file: 'jenkins/Jenkinsfile.production-web-build',
includes: 'npm run check:production-ops',
reason: 'Web 生产构建必须执行生产运维静态门禁。',
},
{
file: 'jenkins/Jenkinsfile.production-web-build',
includes: 'npm run lint:eslint',
reason: 'Web 生产构建必须执行 ESLint 门禁。',
},
{
file: 'jenkins/Jenkinsfile.production-web-build',
includes: 'npm run typecheck',
reason: 'Web 生产构建必须执行主站类型检查。',
},
{
file: 'jenkins/Jenkinsfile.production-web-build',
includes: 'npm run admin-web:typecheck',
reason: 'Web 生产构建必须执行后台类型检查。',
},
{
file: 'jenkins/Jenkinsfile.production-web-build',
includes: 'npm run test',
reason: 'Web 生产构建必须执行当前维护范围内的 Vitest。',
},
{
file: 'server-rs/crates/spacetime-module/src/migration.rs',
includes:
+4 -4
View File
@@ -481,9 +481,9 @@ function getChangedFiles(baseRef) {
tryGit(['ls-files', '--others', '--exclude-standard', '-z', bindingsRoot]) ?? '';
return new Set(
[
...diffOutput.split(/\u0000/u),
...untrackedModuleOutput.split(/\u0000/u),
...untrackedBindingsOutput.split(/\u0000/u),
...diffOutput.split(String.fromCharCode(0)),
...untrackedModuleOutput.split(String.fromCharCode(0)),
...untrackedBindingsOutput.split(String.fromCharCode(0)),
]
.map(normalizePath)
.filter(Boolean),
@@ -575,7 +575,7 @@ function compareTables(baseTables, currentTables) {
}
}
for (const [accessor, table] of currentTables) {
for (const accessor of currentTables.keys()) {
if (!baseTables.has(accessor)) {
schemaChanged = true;
}
@@ -180,10 +180,6 @@ function hasExternalPlatformMarker(line) {
return externalPlatformPatterns.some((pattern) => pattern.test(line));
}
function formatHits(hits) {
return hits.map((hit) => ` - ${hit.file}:${hit.lineNumber} ${hit.text}`);
}
const documentFiles = collectFiles(documentTargets);
const visualNovelFiles = collectFiles(visualNovelImplementationTargets);
@@ -1,8 +1,8 @@
#!/usr/bin/env node
import {
chownSync,
chmodSync,
chownSync,
lstatSync,
mkdtempSync,
readFileSync,
@@ -2,8 +2,8 @@
import { spawnSync } from 'node:child_process';
import {
chownSync,
chmodSync,
chownSync,
lstatSync,
mkdtempSync,
readFileSync,
+2 -2
View File
@@ -1,5 +1,6 @@
#!/usr/bin/env node
import { execFileSync, spawnSync } from 'node:child_process';
import { randomBytes } from 'node:crypto';
import {
accessSync,
@@ -15,9 +16,8 @@ import {
rmSync,
statSync,
} from 'node:fs';
import { execFileSync, spawnSync } from 'node:child_process';
import path from 'node:path';
import { userInfo } from 'node:os';
import path from 'node:path';
const DEFAULT_SERVICE_USER = 'genarrative';
const DEFAULT_SERVICE_GROUP = 'genarrative';
@@ -1,11 +1,12 @@
#!/usr/bin/env node
import { spawn } from 'node:child_process';
import {
callSpacetimeProcedureViaCli,
ensureProcedureOk,
validateSpacetimeDatabaseName,
} from '../spacetime-migration-common.mjs';
import { spawn } from 'node:child_process';
const PROCEDURE = 'rotate_editor_generation_runtime_service_identity_and_return';
+2 -2
View File
@@ -8,9 +8,9 @@ import {
rmSync,
writeFileSync,
} from 'node:fs';
import {createServer} from 'node:net';
import {hostname, userInfo} from 'node:os';
import {dirname, resolve} from 'node:path';
import {createServer} from 'node:net';
const LINUX_DEV_PORT_RANGE_REGISTRY_ROOT = '/var/tmp/genarrative-dev-port-ranges';
const LINUX_DEV_PORT_RANGE_POOL_START = 10000;
@@ -308,7 +308,7 @@ function nextLinuxPortRangeCandidate(registry) {
async function withLinuxPortRangeRegistryLock(lockPath, action) {
ensureWorldWritableDirectory(dirname(lockPath));
while (true) {
for (;;) {
try {
writeFileSync(
lockPath,
+25 -4
View File
@@ -1,8 +1,10 @@
import {createServer} from 'node:net';
import {mkdtempSync, readFileSync, rmSync} from 'node:fs';
import {createServer} from 'node:net';
import {tmpdir} from 'node:os';
import {join} from 'node:path';
import {describe, expect, it} from 'vitest';
import {
findAvailablePort,
mapDevPortsToPortRange,
@@ -23,6 +25,27 @@ function reservePort(port) {
});
}
async function reserveConsecutivePorts() {
for (let attempt = 0; attempt < 20; attempt += 1) {
const firstServer = await reservePort(0);
const firstPort = firstServer.address().port;
if (firstPort === 65535) {
await new Promise((resolve) => firstServer.close(resolve));
continue;
}
try {
const secondServer = await reservePort(firstPort + 1);
return {firstServer, secondServer, firstPort};
} catch {
await new Promise((resolve) => firstServer.close(resolve));
}
}
throw new Error('无法为测试保留两个连续端口');
}
describe('dev stack port utils', () => {
it('解析端口段并映射到四个 dev 端口', () => {
expect(parsePortRangeSpec('10000-10099')).toEqual({
@@ -39,9 +62,7 @@ describe('dev stack port utils', () => {
});
it('使用端口可用性检查为被占用端口寻找后续可用端口', async () => {
const firstServer = await reservePort(0);
const firstPort = firstServer.address().port;
const secondServer = await reservePort(firstPort + 1);
const {firstServer, secondServer, firstPort} = await reserveConsecutivePorts();
try {
const availablePort = await findAvailablePort({
+2 -1
View File
@@ -921,7 +921,8 @@ function readLinuxApiServerProcessSnapshot(pid) {
if (
error?.code === 'ENOENT' ||
error?.code === 'EACCES' ||
error?.code === 'EPERM'
error?.code === 'EPERM' ||
error?.code === 'ESRCH'
) {
return null;
}
+10
View File
@@ -945,6 +945,7 @@ spacetimedb tool version 2.6.0; spacetimedb-lib version 2.6.0;
});
test('本地 API identity 不跟随符号链接记录', async () => {
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
const tempDir = mkdtempSync(join(tmpdir(), 'genarrative-api-identity-'));
try {
const { explicitOptions, options } = parseArgs(
@@ -988,6 +989,9 @@ spacetimedb tool version 2.6.0; spacetimedb-lib version 2.6.0;
identity: 'linked-identity',
token: 'linked-token',
});
expect(warnSpy).toHaveBeenCalledWith(
expect.stringContaining('本地 API identity 记录不可用,将重新创建'),
);
} finally {
rmSync(tempDir, { recursive: true, force: true });
}
@@ -1165,6 +1169,7 @@ spacetimedb tool version 2.6.0; spacetimedb-lib version 2.6.0;
});
test('不复用权限宽松、格式非法或空的本地运行服务 bootstrap secret 记录', () => {
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
const tempDir = mkdtempSync(
join(tmpdir(), 'genarrative-runtime-bootstrap-secret-'),
);
@@ -1234,6 +1239,11 @@ spacetimedb tool version 2.6.0; spacetimedb-lib version 2.6.0;
if (process.platform !== 'win32') {
expect(statSync(secretPath).mode & 0o777).toBe(0o600);
}
expect(warnSpy).toHaveBeenCalledWith(
expect.stringContaining(
'本地运行服务 bootstrap secret 记录不可用,将重新生成',
),
);
} finally {
rmSync(tempDir, { recursive: true, force: true });
}
+2 -2
View File
@@ -1,7 +1,7 @@
import {Buffer} from 'node:buffer';
import {existsSync, mkdirSync, readFileSync, writeFileSync} from 'node:fs';
import path from 'node:path';
import {spawnSync} from 'node:child_process';
import { mkdirSync, readFileSync, writeFileSync} from 'node:fs';
import path from 'node:path';
import {mergeApiServerEnv} from './dev-utils.mjs';
+1 -1
View File
@@ -1,9 +1,9 @@
#!/usr/bin/env node
import { execFile } from 'node:child_process';
import { mkdir, writeFile } from 'node:fs/promises';
import http from 'node:http';
import https from 'node:https';
import { mkdir, writeFile } from 'node:fs/promises';
import { dirname } from 'node:path';
const STATUS_RANK = {
@@ -1,4 +1,5 @@
import { describe, expect, it } from 'vitest';
import { rebindOrphanWorkOwnersInMigration } from './rebind-orphan-work-owners.mjs';
const placeholderUserId = 'wx-openid-placeholder';
@@ -1,4 +1,5 @@
import { describe, expect, it } from 'vitest';
import { repairAuthPublicUserCodesInMigration } from './repair-auth-public-user-codes.mjs';
function table(name: string, rows: unknown[]) {
+1 -1
View File
@@ -1,5 +1,5 @@
import {existsSync} from 'node:fs';
import {spawn} from 'node:child_process';
import {existsSync} from 'node:fs';
const [, , scriptPath, ...scriptArgs] = process.argv;

Some files were not shown because too many files have changed in this diff Show More