From ee8bfa48fd9ca4fa30cbafab55d7f4ab860ed0d9 Mon Sep 17 00:00:00 2001 From: Haitao Pan Date: Sat, 30 May 2026 12:04:54 +0800 Subject: [PATCH] chore: update core integration cases and runtime helpers --- docs/cases/core-integration-manual-cases.md | 40 ++++++++++++++++ ...pp_controller_desktop_runtime_helpers.dart | 46 ++++++++----------- ...troller_thread_workspace_binding_test.dart | 33 +++++++++---- 3 files changed, 83 insertions(+), 36 deletions(-) diff --git a/docs/cases/core-integration-manual-cases.md b/docs/cases/core-integration-manual-cases.md index 2605c83f..51cb4669 100644 --- a/docs/cases/core-integration-manual-cases.md +++ b/docs/cases/core-integration-manual-cases.md @@ -80,6 +80,46 @@ - 测试连接结果 - 是否需要本机服务日志人工对照 +### `MANUAL-ACP-004` 账号同步返回 `bridge_auth_token_unavailable` + +- 前置条件 + - 已登录 `svc.plus` 账号 + - 账户侧能正常拉起 session + - 本地 App 可访问 `https://accounts.svc.plus` +- 典型现象 + - 设置页顶部与账号面板显示 `xworkmate-bridge 连接失败` + - 账号同步状态为 `失败` + - 同步说明显示 `bridge auth token is unavailable` + - 详情信息里可见 `bridge_auth_token_unavailable` +- 排查步骤 + 1. 在 App 中触发账号同步或重新进入设置页 + 2. 直接请求 `GET /api/auth/xworkmate/profile/sync` + 3. 确认是否返回 `409` + 4. 检查本地 secure storage 中是否存在 `xworkmate.account.managed.bridge.auth_token` + 5. 检查 `xworkmate-app` 的 `accountSyncState` 是否被写成 `blocked` + 6. 回到 `accounts.svc.plus` 服务侧确认 shared bridge token 是否已注入 Vault 并可被读取 +- 期望结论 + - 若接口返回 `409 bridge_auth_token_unavailable`,问题不在 App 的 bridge 连接逻辑 + - 根因在 `accounts.svc.plus` 的 shared XWorkmate bridge token 供给链路 + - `BRIDGE_AUTH_TOKEN` 需要由 accounts 服务通过 Vault 读取并下发 +- 关联同步规则 + - 任务 workspace 需要递归同步全部子目录和文件 + - `dist/账户与身份安全演进史-GPT混排最终版.pdf` 这类深层产物应能回传到当前线程 artifact + - 如 skill 或任务已有忽略清单,则中间产物可按清单排除同步,常见对象包括草稿、临时文件、版本号中间稿和可重复生成缓存 + - 忽略清单建议以约定文件形式维护,例如 `.ignore.md` +- 恢复条件 + - `GET /api/auth/xworkmate/profile/sync` 返回 `200` + - 响应包含 `BRIDGE_SERVER_URL` 和 `BRIDGE_AUTH_TOKEN` + - App 重新同步后,`accountSyncState.syncState` 变为 `ready` + - 本地 managed secret `bridge.auth_token` 被写入 +- 建议记录项 + - 登录账号 + - `profile/sync` HTTP 状态码 + - 响应错误码或成功字段 + - 本地 `accountSyncState.syncState` + - `tokenConfigured.bridge` 是否为 `true` + - 是否已在 `accounts.svc.plus` 侧确认 Vault / bootstrap 配置 + ## 3. 本地执行型任务线程 ### `MANUAL-LOCAL-001` `powerpoint-pptx` diff --git a/lib/app/app_controller_desktop_runtime_helpers.dart b/lib/app/app_controller_desktop_runtime_helpers.dart index f7ce4baa..dc7904af 100644 --- a/lib/app/app_controller_desktop_runtime_helpers.dart +++ b/lib/app/app_controller_desktop_runtime_helpers.dart @@ -777,7 +777,6 @@ extension AppControllerDesktopRuntimeHelpers on AppController { var wroteArtifact = false; var failedArtifact = false; var skippedArtifact = false; - final currentTaskArtifactRelativePaths = []; for (final artifact in artifacts) { final relativePath = _sanitizeArtifactRelativePathInternal( artifact.relativePath, @@ -799,10 +798,6 @@ extension AppControllerDesktopRuntimeHelpers on AppController { continue; } wroteArtifact = true; - _appendArtifactRelativePathsInternal( - currentTaskArtifactRelativePaths, - existingArtifactPaths, - ); continue; } final target = await _nextArtifactTargetFileInternal(root, relativePath); @@ -817,17 +812,6 @@ extension AppControllerDesktopRuntimeHelpers on AppController { continue; } wroteArtifact = true; - final writtenRelativePath = - DesktopThreadArtifactService.relativePathInternal( - root.path, - target.path, - ); - if (writtenRelativePath != null && writtenRelativePath.isNotEmpty) { - _appendArtifactRelativePathsInternal( - currentTaskArtifactRelativePaths, - [writtenRelativePath], - ); - } } final syncStatus = wroteArtifact @@ -835,6 +819,9 @@ extension AppControllerDesktopRuntimeHelpers on AppController { : failedArtifact ? 'download-failed' : 'no-artifacts'; + final currentTaskArtifactRelativePaths = wroteArtifact + ? await _collectWorkspaceArtifactRelativePathsInternal(root) + : const []; upsertTaskThreadInternal( normalizedSessionKey, lastArtifactSyncAtMs: syncedAtMs, @@ -1158,17 +1145,6 @@ extension AppControllerDesktopRuntimeHelpers on AppController { ) => kGatewayRemoteProfileIndex; } -void _appendArtifactRelativePathsInternal( - List target, - List paths, -) { - for (final path in paths) { - if (!target.contains(path)) { - target.add(path); - } - } -} - Future> _existingWorkspaceArtifactPathsInternal( Directory root, String relativePath, @@ -1209,6 +1185,22 @@ Future> _existingWorkspaceArtifactPathsInternal( return paths; } +Future> _collectWorkspaceArtifactRelativePathsInternal( + Directory root, +) async { + final files = await DesktopThreadArtifactService().collectFilesInternal(root); + final paths = []; + for (final file in files) { + final resolvedRelativePath = + DesktopThreadArtifactService.relativePathInternal(root.path, file.path); + if (resolvedRelativePath != null && resolvedRelativePath.isNotEmpty) { + paths.add(resolvedRelativePath); + } + } + paths.sort(); + return paths; +} + String _normalizeAuthorizationHeaderInternal(String raw) { final trimmed = raw.trim(); if (trimmed.isEmpty) { diff --git a/test/runtime/app_controller_thread_workspace_binding_test.dart b/test/runtime/app_controller_thread_workspace_binding_test.dart index 63a67609..26112044 100644 --- a/test/runtime/app_controller_thread_workspace_binding_test.dart +++ b/test/runtime/app_controller_thread_workspace_binding_test.dart @@ -492,9 +492,10 @@ void main() { final snapshot = await controller.loadAssistantArtifactSnapshot( sessionKey: 'unit-fixture-task-a', ); - expect(snapshot.resultEntries.map((entry) => entry.relativePath), [ - 'notes/hello.v2.txt', - ]); + expect( + snapshot.resultEntries.map((entry) => entry.relativePath), + containsAll(['notes/hello.v2.txt', 'notes/hello.txt']), + ); expect( snapshot.fileEntries.map((entry) => entry.relativePath), containsAll(['notes/hello.v2.txt', 'notes/hello.txt']), @@ -566,7 +567,10 @@ void main() { final currentRelativePaths = snapshot.resultEntries .map((entry) => entry.relativePath) .toList(growable: false); - expect(currentRelativePaths, ['current-task-report.md']); + expect( + currentRelativePaths, + containsAll(['current-task-report.md', 'old-task-report.md']), + ); expect( snapshot.fileEntries.map((entry) => entry.relativePath), containsAll(['current-task-report.md', 'old-task-report.md']), @@ -615,6 +619,10 @@ void main() { await File( '${localWorkspace.path}/chapters/codex-chapter-breakdown.md', ).create(recursive: true); + await Directory('${localWorkspace.path}/dist').create(recursive: true); + await File( + '${localWorkspace.path}/dist/账户与身份安全演进史-GPT混排最终版.pdf', + ).writeAsBytes([7, 8, 9]); controller.upsertTaskThreadInternal( 'unit-fixture-task-a', @@ -657,6 +665,7 @@ void main() { 'assets/images/chapters/chapter-1.png', 'assets/images/cover.png', 'chapters/codex-chapter-breakdown.md', + 'dist/账户与身份安全演进史-GPT混排最终版.pdf', ]); final snapshot = await controller.loadAssistantArtifactSnapshot( sessionKey: 'unit-fixture-task-a', @@ -667,6 +676,7 @@ void main() { 'assets/images/chapters/chapter-1.png', 'assets/images/cover.png', 'chapters/codex-chapter-breakdown.md', + 'dist/账户与身份安全演进史-GPT混排最终版.pdf', ]), ); }); @@ -989,12 +999,17 @@ void main() { await File('${localWorkspace.path}/reports/resume.bin').readAsBytes(), body, ); - expect( - controller - .requireTaskThreadForSessionInternal('unit-fixture-task-a') - .lastArtifactSyncStatus, - 'synced', + final thread = controller.requireTaskThreadForSessionInternal( + 'unit-fixture-task-a', ); + for ( + var attempt = 0; + attempt < 20 && thread.lastArtifactSyncStatus != 'synced'; + attempt += 1 + ) { + await Future.delayed(const Duration(milliseconds: 10)); + } + expect(thread.lastArtifactSyncStatus, 'synced'); }, );