From 6f9fd0f0bdc9aa792f29aca8413cab6baa0fd45b Mon Sep 17 00:00:00 2001 From: inman Date: Wed, 2 Sep 2026 15:09:07 +0800 Subject: [PATCH] feat: bind AgentBus work to account workers --- ...60902-agentbus-account-routing-b62f19e4.md | 65 ++++ LianSyn-platform/app-account-form.test.mjs | 14 +- LianSyn-platform/app.js | 233 ++++++++++++-- LianSyn-platform/index.html | 8 +- agent设计规范/agentbus-reply-contract.md | 8 + agent设计规范/business-adaptation-registry.md | 2 +- .../test-fixtures/lwlt-lifecycle/release-gate.md | 4 +- archive/releases/2026-09-02/README.md | 6 + .../ltjt-order-assistant-0.5.163.zip | Bin .../2026-09-02/release-manifest-0.5.163.json | 69 +++++ .../ltjt-order-assistant/README.md | 7 +- .../ltjt-order-assistant/background.js | 15 +- .../ltjt-order-assistant/business-bridge.js | 9 +- .../ltjt-order-assistant/inpage.js | 2 +- .../ltjt-order-assistant/manifest.json | 2 +- .../ltjt-order-assistant/team-batch-inpage.js | 2 +- control-plane/README.md | 24 +- .../018_agentbus_account_workers.sql | 100 +++++++ control-plane/src/agentbus-channels.ts | 202 ++++++++++--- control-plane/src/agentbus.ts | 8 +- control-plane/src/auth.ts | 139 +++++++-- control-plane/src/db.ts | 2 +- control-plane/src/server.ts | 38 ++- control-plane/src/task-service.ts | 283 +++++++++++++----- .../test/account-authorization.test.ts | 52 +++- control-plane/test/agentbus.test.ts | 19 +- control-plane/test/control-plane.test.ts | 36 ++- dist/ltjt-order-assistant-0.5.164.zip | Bin 0 -> 213669 bytes dist/release-manifest.json | 10 +- mappings/lifecycle.mapping.json | 2 +- tools/lifecycle-contract.test.mjs | 15 +- 31 files changed, 1119 insertions(+), 257 deletions(-) create mode 100644 .project-docs/30-worklog/tasks/20260902-agentbus-account-routing-b62f19e4.md create mode 100644 archive/releases/2026-09-02/README.md rename {dist => archive/releases/2026-09-02}/ltjt-order-assistant-0.5.163.zip (100%) create mode 100644 archive/releases/2026-09-02/release-manifest-0.5.163.json create mode 100644 control-plane/migrations/018_agentbus_account_workers.sql create mode 100644 dist/ltjt-order-assistant-0.5.164.zip diff --git a/.project-docs/30-worklog/tasks/20260902-agentbus-account-routing-b62f19e4.md b/.project-docs/30-worklog/tasks/20260902-agentbus-account-routing-b62f19e4.md new file mode 100644 index 0000000..45b7bc0 --- /dev/null +++ b/.project-docs/30-worklog/tasks/20260902-agentbus-account-routing-b62f19e4.md @@ -0,0 +1,65 @@ +# Task: Bind AgentBus channels to account workers + +## Identity + +- Task ID: 20260902-agentbus-account-routing-b62f19e4 +- Mode: Feature +- Branch: codex/20260902-agentbus-account-routing-b62f19e4-agentbus-account-routing-b62f19e4 +- Worktree: /Users/inmanx/Documents/lwltAPI-agentbus-account-routing-b62f19e4 +- Base commit: d034f649c4e7c5d0856f22053b92d2e5a63be5eb +- Owner: codex +- Status: Ready for integration + +## Scope + +- Add a one-to-one AgentBus channel owner on platform employee accounts and stamp every new task with an immutable execution assignee. +- Route inbound AgentBus messages through the bound employee's identity and existing business-route allowlist. +- Keep administrators able to manage and inspect channels/tasks while preventing them from claiming another account's ERP work. +- Make one fresh browser connection the only execution worker for each account and fail closed when the expected ERP account is missing or does not match. +- Extend account/channel administration UI, browser heartbeat, extension ERP-session inspection, migrations, contracts, tests, and the versioned extension release. + +## Intent And Constraints + +- User-authorized routing model: each employee platform account binds one AgentBus channel and one expected ERP account; administrator accounts remain unbound from employee channels. +- Preserve organization-wide ERP FIFO serialization; this task does not authorize parallel ERP writes. +- Existing unbound AgentBus channels and historical unassigned AgentBus tasks must not execute automatically. +- Browser worker failover is automatic only after the previous worker heartbeat becomes stale; concurrent cloud PCs must not race or alternate ownership. +- No production database migration, deployment, service restart, or real ERP write is authorized in this task. +- Preserve unrelated dashboard-filter work in the main worktree; integration must reconcile the known overlap in `control-plane/src/task-service.ts` and `LianSyn-platform/app.js`. + +## Outcome + +- Added migration `018_agentbus_account_workers`: non-admin accounts can carry a case-insensitively unique expected ERP account; each AgentBus channel has one scoped employee owner; every new task stores its execution assignee; existing manual tasks are backfilled while historical AgentBus tasks remain deliberately unassigned; one connected browser worker is allowed per account. +- AgentBus channel creation/update now requires an active `user` or `team_lead` with an ERP account. One account cannot own multiple channels, one AgentBus key cannot fan out across channels, enabled unbound legacy channels stay stopped, and listener intake rechecks that its runtime owner still matches the database owner. +- AgentBus intake executes under the bound employee identity and business-route allowlist. The assignee is stamped when the task is created and is not re-inferred from whichever cloud PC happens to be online. +- Ordinary employees and team leads can read and execute their own assigned manual and AgentBus work. Administrators retain organization-wide inspection and management visibility, but confirmation, browser claim, result submission, reconciliation, and resume require the exact task assignee. +- Browser heartbeats verify the expected ERP account, register only a matching session as execution-ready, reject a second fresh worker for the same account, and permit failover only after the earlier heartbeat is stale for 90 seconds. A mismatched cloud PC is recorded as `identity_mismatch` and cannot displace a valid worker. +- Updated the account/channel administration UI, employee-only executable task feeds, assignment audit display, bridge heartbeat, and extension ERP-session probe. Released extension `0.5.164`; its probe receives the expected account and returns only the match boolean rather than page account text. +- Archived the superseded `0.5.163` ZIP and manifest snapshot under `archive/releases/2026-09-02/`; the current versioned artifact is `dist/ltjt-order-assistant-0.5.164.zip` with SHA-256 `5a59b616aa7ea2cfcc02b2c3242bfc0b424dcd6ddd28fff7ed9bcfb5b367191a`. + +## Verification + +- TypeScript no-emit check: passed. +- TypeScript build to ignored `.build/`: passed. +- Full control-plane regression: passed 158/158. +- Full legacy/platform/extension/tools regression: passed 264/264. +- Repository hygiene and release/source/hash checks: passed 10/10. +- Focused account/AgentBus routing regression: passed 29/29. +- JavaScript syntax checks for the platform app and modified extension bridge/background files: passed. +- `git diff --check`: passed. +- No live PostgreSQL migration, deployed-service smoke test, live multi-cloud-PC staging exercise, extension reload, ERP read, or ERP write was performed. + +## Follow-ups + +- Integration must reconcile the known overlap with task `20260902-kanban-filter-7e3a91c4` in `control-plane/src/task-service.ts` and `LianSyn-platform/app.js`; retain both the kanban filtering behavior and assignee-only executable feeds. +- Production rollout requires separate authorization: back up PostgreSQL, apply migration 018, deploy/restart the control plane and platform, load extension 0.5.164 on each employee cloud PC, configure each employee ERP account and route allowlist, then bind one independent AgentBus channel/key to each employee. +- Before production assurance, perform a staging matrix with at least two employee accounts/cloud PCs/ERP logins, an administrator browser left open, a deliberately mismatched ERP login, a same-account two-device conflict, 90-second stale-worker failover, and both manual and automatic AgentBus tasks. + +## Promotion Candidates + +- Target canonical documents: `.project-docs/20-architecture/system-overview.md`, `.project-docs/20-architecture/data-flow.md`, `.project-docs/40-domain/business-rules.md`, and the relevant authentication/authorization ADR during a later Integration Gate. +- Proposal: make the supported routing chain explicit as `AgentBus channel -> employee platform account -> immutable task assignee -> one fresh browser worker -> matching ERP account`; administrators manage and inspect but do not execute another assignee's work. +- Evidence: migration 018, channel owner/key gates, task-assignee access and execution checks, heartbeat worker/ERP checks, employee-only polling, updated extension probe, release 0.5.164, and the verification results above. +- Future impact: account onboarding, AgentBus key management, task APIs/SSE, automatic dispatch, cloud-PC failover, ERP identity handling, administrator workflows, deployment ordering, and staging acceptance. +- Semantic conflicts: this supersedes the prior behavior in which AgentBus tasks had no employee owner and administrators could claim globally visible work; organization-wide ERP FIFO serialization remains unchanged. Integration also has a code overlap with the kanban-filter task that is mechanical unless either task changed the meaning of active/status filters. +- Human confirmation required: yes for canonical promotion, conflict resolution if integration reveals a behavioral difference, production migration/deploy/restart, extension rollout, and live staging or ERP access. diff --git a/LianSyn-platform/app-account-form.test.mjs b/LianSyn-platform/app-account-form.test.mjs index 4537997..9cdb5a1 100644 --- a/LianSyn-platform/app-account-form.test.mjs +++ b/LianSyn-platform/app-account-form.test.mjs @@ -37,12 +37,18 @@ test('account creation accepts any non-empty password and blocks an empty passwo const empty = validate({ username: 'operator', password: '', - role: 'user' + role: 'user', + erpAccount: 'erp-operator' }); assert.equal(empty.ok, false); assert.equal(empty.field, 'accountPassword'); assert.equal(empty.message, '请输入初始密码。'); - const oneCharacter = validate({ username: 'operator', password: '1', role: 'user' }); + const oneCharacter = validate({ + username: 'operator', + password: '1', + role: 'user', + erpAccount: 'erp-operator' + }); assert.equal(oneCharacter.ok, true); const createStart = appSource.indexOf('async function createAccountFromForm()'); const createEnd = appSource.indexOf('\n}\n\nasync function updateManagedAccount', createStart); @@ -58,12 +64,14 @@ test('account creation normalizes valid form values into the server contract', ( const result = validate({ username: ' TeamLead ', password: '123456', - role: 'team_lead' + role: 'team_lead', + erpAccount: ' ERP-TeamLead ' }); assert.equal(result.ok, true); assert.equal(result.body.username, 'TeamLead'); assert.equal(result.body.password, '123456'); assert.equal(result.body.role, 'team_lead'); + assert.equal(result.body.erp_account, 'ERP-TeamLead'); assert.equal(Object.hasOwn(result.body, 'must_change_password'), false); assert.equal(Array.isArray(result.body.business_route_ids), true); assert.equal(result.body.business_route_ids.length, 0); diff --git a/LianSyn-platform/app.js b/LianSyn-platform/app.js index 7038943..4f4d4b0 100644 --- a/LianSyn-platform/app.js +++ b/LianSyn-platform/app.js @@ -97,7 +97,7 @@ const persistedExtensionResultVersions = new Map(); let taskCreateInProgress = false; const AUTO_HANDOFF_RETRY_MS = 30_000; -const REQUIRED_EXTENSION_VERSION = '0.5.163'; +const REQUIRED_EXTENSION_VERSION = '0.5.164'; const MANUAL_HANDOFF_LABEL = '确认并提交到 ERP 插件'; const RETRY_HANDOFF_LABEL = '继续提交到 ERP 插件'; const RECONCILE_LABEL = '只读回查 ERP 现有结果'; @@ -737,10 +737,33 @@ function renderChannelList() { heading.append(el('span', `state ${channel.status === 'connected' ? 'state-ok' : channel.status === 'error' ? 'state-bad' : 'state-warn'}`, channelStatusLabel(channel.status))); main.append(heading); main.append(el('p', 'muted', channel.external_user_ref ? `外部用户:${channel.external_user_ref}` : '未设置外部用户标识')); + main.append(el( + 'p', + channel.routing_ready ? 'muted' : 'channel-error', + channel.owner_username + ? `执行账号:${channel.owner_username} · ERP:${channel.owner_erp_account || '未配置'}` + : '未绑定平台员工账号,渠道不会接收任务' + )); main.append(el('p', 'channel-key-state', 'AgentBus key:已配置(平台不会回显)')); if (channel.last_error) main.append(el('p', 'channel-error', channel.last_error)); row.append(main); const actions = el('div', 'channel-row-actions'); + const owner = document.createElement('select'); + owner.dataset.channelAction = 'owner'; + owner.dataset.channelId = channel.id; + const availableOwners = accountList.filter((account) => ( + account.role !== 'admin' + && account.is_active + && account.erp_account + && (account.id === channel.owner_user_id || !channelList.some((item) => item.id !== channel.id && item.owner_user_id === account.id)) + )); + owner.append(new Option('选择执行账号', '')); + for (const account of availableOwners) { + owner.append(new Option(`${account.username} · ${account.erp_account}`, account.id)); + } + owner.value = channel.owner_user_id || ''; + owner.disabled = channelSettingsBusy; + actions.append(owner); const toggle = el('button', 'secondary-button', channel.enabled ? '停用' : '启用'); toggle.type = 'button'; toggle.dataset.channelAction = 'toggle'; @@ -776,9 +799,23 @@ async function syncChannels() { const result = await apiRequest('/api/channels'); channelList = Array.isArray(result.channels) ? result.channels : []; renderChannelList(); + renderChannelOwnerOptions(); return channelList; } +function renderChannelOwnerOptions() { + const select = $('#channelOwnerUserId'); + if (!select) return; + const selected = String(select.value || ''); + select.replaceChildren(new Option('请选择员工账号', '')); + const boundIds = new Set(channelList.map((channel) => channel.owner_user_id).filter(Boolean)); + for (const account of accountList) { + if (account.role === 'admin' || !account.is_active || !account.erp_account || boundIds.has(account.id)) continue; + select.append(new Option(`${account.username} · ERP ${account.erp_account}`, account.id)); + } + if ([...select.options].some((option) => option.value === selected)) select.value = selected; +} + async function createChannelFromForm() { if (!authUser || channelSettingsBusy) return; const message = $('#channelMessage'); @@ -789,6 +826,7 @@ async function createChannelFromForm() { method: 'POST', body: { display_name: normalizeText($('#channelDisplayName')?.value).slice(0, 120), + owner_user_id: String($('#channelOwnerUserId')?.value || ''), external_user_ref: normalizeText($('#channelExternalUserRef')?.value).slice(0, 200), agentbus_key: String($('#channelAgentbusKey')?.value || '').trim(), bot_address: normalizeText($('#channelBotAddress')?.value).slice(0, 200), @@ -798,9 +836,11 @@ async function createChannelFromForm() { if (result.channel) channelList = [...channelList.filter((item) => item.id !== result.channel.id), result.channel]; $('#channelAgentbusKey').value = ''; $('#channelDisplayName').value = ''; + $('#channelOwnerUserId').value = ''; $('#channelExternalUserRef').value = ''; $('#channelBotAddress').value = ''; renderChannelList(); + renderChannelOwnerOptions(); if (message) message.textContent = '渠道已保存,连接状态会在服务端异步更新。'; } finally { channelSettingsBusy = false; @@ -821,6 +861,23 @@ async function updateChannelEnabled(channelId, enabled) { } } +async function updateChannelOwner(channelId, ownerUserId) { + if (!authUser || channelSettingsBusy || !ownerUserId) return; + channelSettingsBusy = true; + try { + await apiRequest(`/api/channels/${encodeURIComponent(channelId)}`, { + method: 'PATCH', + body: { owner_user_id: ownerUserId } + }); + await syncChannels(); + renderChannelOwnerOptions(); + } finally { + channelSettingsBusy = false; + renderChannelList(); + renderChannelOwnerOptions(); + } +} + async function renameChannel(channelId) { if (!authUser || channelSettingsBusy) return; const channel = channelList.find((item) => item.id === channelId); @@ -893,6 +950,7 @@ function validateAccountCreationValues(values = {}) { const username = String(values.username || '').trim(); const password = String(values.password || ''); const role = String(values.role || ''); + const erpAccount = String(values.erpAccount || '').trim(); if (!username || username.length > 160) { return { ok: false, field: 'accountUsername', message: '账号必须为 1—160 个字符。' }; } @@ -902,12 +960,16 @@ function validateAccountCreationValues(values = {}) { if (!['admin', 'team_lead', 'user'].includes(role)) { return { ok: false, field: 'accountRole', message: '请选择有效的账号角色。' }; } + if (role !== 'admin' && (!erpAccount || erpAccount.length > 200)) { + return { ok: false, field: 'accountErpAccount', message: '普通用户或组长必须填写 1—200 个字符的 ERP 账号。' }; + } return { ok: true, body: { username, password, role, + erp_account: role === 'admin' ? '' : erpAccount, business_route_ids: [] } }; @@ -925,6 +987,9 @@ function accountCreationErrorMessage(error) { if (details.some((path) => path === 'role' || path.startsWith('role.'))) { return '请选择有效的账号角色。'; } + if (details.some((path) => path === 'erp_account' || path.startsWith('erp_account.'))) { + return '普通用户或组长必须填写 ERP 账号。'; + } } return error?.message || String(error); } @@ -940,7 +1005,7 @@ function showAccountCreationValidationError(validation) { } function clearAccountCreationValidationErrors() { - for (const fieldId of ['accountUsername', 'accountPassword', 'accountRole']) { + for (const fieldId of ['accountUsername', 'accountPassword', 'accountRole', 'accountErpAccount']) { document.getElementById(fieldId)?.removeAttribute('aria-invalid'); } } @@ -964,6 +1029,9 @@ function renderAccounts() { heading.append(el('span', `state ${account.is_active ? 'state-ok' : 'state-bad'}`, account.is_active ? '有效' : '已停用')); main.append(heading); main.append(el('p', 'muted', accountRoleLabel(account.role))); + main.append(el('p', account.erp_account ? 'muted' : 'channel-error', account.role === 'admin' + ? 'ERP 执行身份:不绑定' + : `ERP 执行身份:${account.erp_account || '未配置(禁止执行)'}`)); const authorizedCount = Array.isArray(account.authorized_business_route_ids) ? account.authorized_business_route_ids.length : 0; @@ -1002,7 +1070,12 @@ function renderAccounts() { authorizations.dataset.accountAction = 'business-authorizations'; authorizations.dataset.accountId = account.id; authorizations.disabled = accountSettingsBusy || account.role === 'admin'; - actions.append(role, authorizations, toggle, reset, revoke); + const erpAccount = el('button', 'secondary-button', 'ERP 账号'); + erpAccount.type = 'button'; + erpAccount.dataset.accountAction = 'erp-account'; + erpAccount.dataset.accountId = account.id; + erpAccount.disabled = accountSettingsBusy || account.role === 'admin'; + actions.append(role, authorizations, erpAccount, toggle, reset, revoke); row.append(main, actions); container.append(row); } @@ -1098,6 +1171,7 @@ async function syncAccounts() { accountList = Array.isArray(result.accounts) ? result.accounts : []; accountTaskTypes = Array.isArray(result.task_types) ? result.task_types : []; renderAccounts(); + renderChannelOwnerOptions(); } async function createAccountFromForm() { @@ -1105,7 +1179,8 @@ async function createAccountFromForm() { const validation = validateAccountCreationValues({ username: $('#accountUsername').value, password: $('#accountPassword').value, - role: $('#accountRole').value + role: $('#accountRole').value, + erpAccount: $('#accountErpAccount').value }); if (!validation.ok) { showAccountCreationValidationError(validation); @@ -1124,6 +1199,7 @@ async function createAccountFromForm() { .sort((left, right) => left.username.localeCompare(right.username, 'zh-CN')); $('#accountUsername').value = ''; $('#accountPassword').value = ''; + $('#accountErpAccount').value = ''; if (message) { message.textContent = result.account?.role === 'admin' ? '管理员账号已创建;该角色固定拥有全部任务权限,初始密码不会再次显示。' @@ -1135,6 +1211,14 @@ async function createAccountFromForm() { } } +async function updateManagedAccountErpIdentity(accountId) { + const account = accountList.find((item) => item.id === accountId); + if (!account || account.role === 'admin') return; + const value = window.prompt('请输入 ERP 页面显示的登录账号。修改后该员工的现有平台会话和云电脑执行连接会失效:', account.erp_account || ''); + if (value === null || !value.trim() || value.trim() === account.erp_account) return; + await updateManagedAccount(accountId, { erp_account: value.trim().slice(0, 200) }); +} + async function updateManagedAccount(accountId, patch) { if (!isAdministrator() || accountSettingsBusy) return; accountSettingsBusy = true; @@ -1807,12 +1891,12 @@ function runtimeTasks() { } function hasPollableRuntimeTasks() { - return runtimeTasks().some(isTaskPollable); + return runtimeTasks().some((task) => taskAssignedToCurrentAccount(task) && isTaskPollable(task)); } async function syncRuntimeTasks() { if (!authUser) return; - const result = await apiRequest('/api/tasks?status=active&limit=200&include_total=false'); + const result = await apiRequest('/api/tasks?status=active&limit=200&include_total=false&executable_by=me'); const activeTasks = (Array.isArray(result.tasks) ? result.tasks : []) .map(mergeRemoteTask) .filter((task) => !locallyDeletedTaskIds.has(task?.task_id)); @@ -2162,6 +2246,10 @@ function requiresManualConfirmation(task) { return ['awaiting_confirmation', 'agent_parse_passed'].includes(status); } +function taskAssignedToCurrentAccount(task) { + return Boolean(authUser?.id && task?.assignee?.id === authUser.id); +} + function taskCanonicalStatus(task) { return String(task?.status || task?.result?.status || 'created'); } @@ -2496,13 +2584,11 @@ async function autoDispatchReadyTasks({ force = false } = {}) { if (!authUser || autoHandoffInProgress) return; autoHandoffInProgress = true; try { - const result = await apiRequest('/api/tasks?status=confirmed&limit=200&include_total=false'); - const byId = new Map(taskStore.map((task) => [task.task_id, task])); - for (const task of Array.isArray(result.tasks) ? result.tasks : []) { - byId.set(task.task_id, task); + const result = await apiRequest('/api/tasks?status=confirmed&limit=200&include_total=false&executable_by=me'); + const candidates = Array.isArray(result.tasks) ? result.tasks : []; + for (const task of candidates) { cacheRuntimeTask(task); } - const candidates = [...byId.values()]; const orderedCandidates = [...candidates].sort((left, right) => { const leftTime = Date.parse(left?.created_at || '') || 0; const rightTime = Date.parse(right?.created_at || '') || 0; @@ -3498,6 +3584,7 @@ function renderTaskInputAudit(task) { const panel = el('section', 'task-output-panel'); panel.append(el('div', 'task-output-label', '创建人与原始输入审计')); panel.append(el('p', 'muted', `创建人:${task.creator?.username || (task.source === 'agentbus' ? 'AgentBus / 系统' : '历史记录未知')}`)); + panel.append(el('p', task.assignee?.username ? 'muted' : 'channel-error', `执行归属:${task.assignee?.username || '未分配(禁止 ERP 执行)'}`)); const history = taskInputHistoryStore.get(task.task_id); if (!history) { panel.append(el('p', 'muted', '正在加载加密输入历史…')); @@ -3546,10 +3633,11 @@ function renderTaskDetail() { const importantMessagePanel = renderTaskImportantMessage(task); renderTaskStages(task); if (confirmButton) { - const canConfirm = Boolean(task && requiresManualConfirmation(task)); - const canStart = Boolean(task && !isAutomaticTask(task) && canStartConfirmedTask(task)); - const canResume = Boolean(task && canResumePrewriteTask(task)); - const canReconcile = Boolean(task && canReconcileTask(task)); + const canOperate = Boolean(task && taskAssignedToCurrentAccount(task)); + const canConfirm = Boolean(canOperate && requiresManualConfirmation(task)); + const canStart = Boolean(canOperate && !isAutomaticTask(task) && canStartConfirmedTask(task)); + const canResume = Boolean(canOperate && canResumePrewriteTask(task)); + const canReconcile = Boolean(canOperate && canReconcileTask(task)); const reconciliationBusy = Boolean(task && taskReconciliationStates.get(task.task_id) === 'running'); confirmButton.hidden = !canConfirm && !canStart && !canResume && !canReconcile; confirmButton.disabled = (!canConfirm && !canStart && !canResume && !canReconcile) || reconciliationBusy; @@ -4233,6 +4321,9 @@ function renderStatusDetails() { statusDetailRow('ERP 自动化', statusBoolean(status?.automationEnabled, '已开启', '已关闭')), statusDetailRow('ERP 页面权限', statusBoolean(status?.erpHostAccessReady, '可访问', '需在扩展设置中允许')), statusDetailRow('ERP 会话', statusBoolean(status?.erpSessionReady, '正常', '异常或待登录')), + statusDetailRow('ERP 账号匹配', authUser?.erp_account + ? statusBoolean(status?.erpAccountMatched, '与平台绑定一致', `应登录 ${authUser.erp_account}`) + : '管理员不绑定员工 ERP 账号'), statusDetailRow('ERP 链路恢复', recoveryStatus || '未触发'), statusDetailRow('安装时间', formatStatusTime(status?.installedAt)), statusDetailRow('最近错误', status?.error || '无'), @@ -4317,8 +4408,10 @@ function applyBridgePayload(payload = {}) { 'erp_page_access_denied' ]).has(String(erpSession.error_code || '')); erpHostAccessReady = erpSession.host_permission_granted !== false && !erpAccessError; + const erpAccountMatched = !authUser?.erp_account || erpSession.account_matched === true; erpSessionReady = erpSession.session_ready !== false - && erpSession.erp_keepalive?.last_status !== 'session_expired'; + && erpSession.erp_keepalive?.last_status !== 'session_expired' + && erpAccountMatched; const recoveryStatus = String(erpSession.erp_link_recovery?.status || erpSession.erp_keepalive?.recovery_status || ''); const erpLinkRecoveryPending = ['scheduled', 'already_running', 'running'].includes(recoveryStatus); const parseVersion = (value) => String(value || '').split('.').map((part) => Number(part) || 0); @@ -4340,6 +4433,7 @@ function applyBridgePayload(payload = {}) { automationEnabled: erpAutomationEnabled, erpHostAccessReady, erpSessionReady, + erpAccountMatched, erpSession, erpLinkRecovery: { status: recoveryStatus || 'not_started', @@ -4353,7 +4447,9 @@ function applyBridgePayload(payload = {}) { ? 'ERP 自动化开关已关闭。' : !erpHostAccessReady ? erpSession.message || '扩展暂时没有 ERP 页面访问权限。' - : !erpSessionReady + : !erpAccountMatched + ? `当前 ERP 登录账号与平台绑定的“${authUser?.erp_account || ''}”不一致。` + : !erpSessionReady ? erpSession.message || 'ERP 会话异常,插件已尝试刷新现有 ERP 链路;请确认 ERP 已登录。' : erpLinkRecoveryPending ? '已检测到 ERP 链路异常,插件正在刷新并进行只读复测。' @@ -4562,6 +4658,9 @@ async function parseRawInstruction(rawText, taskId) { } async function handoffTaskToExtension(task) { + if (!taskAssignedToCurrentAccount(task)) { + throw new Error('该任务分配给其他平台账号,当前云电脑只能查看,不能执行。'); + } const automaticTask = isAutomaticTask(task); if (!task?.confirmed_at) { throw new Error(automaticTask ? '自动任务缺少确认时间,已停止下发。' : '任务尚未人工确认,不能提交到 ERP 插件。'); @@ -4735,6 +4834,9 @@ async function handoffTaskToExtension(task) { async function confirmAndSubmitToErpPlugin(task) { task = await ensureTaskDetails(task?.task_id); if (!requiresManualConfirmation(task)) return; + if (!taskAssignedToCurrentAccount(task)) { + throw new Error('该任务分配给其他平台账号,当前账号只能查看,不能确认或执行。'); + } const bridgeReady = await pingBridge(); if (!bridgeReady || !extensionCompatible || !erpAutomationEnabled || !erpHostAccessReady || !erpSessionReady || latestBridgeStatus?.erpLinkRecovery?.pending === true) { throw new Error(`插件未连接、版本低于 ${REQUIRED_EXTENSION_VERSION}、ERP 页面/会话不可用或 ERP 操作开关未开启;任务尚未确认,也未提交到插件。`); @@ -4767,19 +4869,49 @@ async function confirmAndSubmitToErpPlugin(task) { async function pingBridge() { try { - const result = await sendToExtension('PING', {}, 1200); + const result = await sendToExtension('PING', { + expected_erp_account: authUser?.erp_account || '' + }, 1200); applyBridgePayload(result); if (authUser && result?.ok) { - await apiRequest('/api/connections/heartbeat', { - method: 'POST', - body: { - connection_id: browserConnectionId, - extension_version: result.version || '', - metadata: { bridge_installed_at: result.bridge_installed_at || '' } + try { + const heartbeat = await apiRequest('/api/connections/heartbeat', { + method: 'POST', + body: { + connection_id: browserConnectionId, + extension_version: result.version || '', + erp_account: authUser.erp_account || '', + erp_account_matched: result.erp_session?.account_matched === true, + metadata: { bridge_installed_at: result.bridge_installed_at || '' } + } + }); + if (heartbeat.execution_ready !== true) { + erpSessionReady = false; + latestBridgeStatus = { + ...latestBridgeStatus, + erpSessionReady: false, + erpAccountMatched: heartbeat.erp_account_matched === true, + error: authUser.erp_account + ? `当前 ERP 登录账号与平台绑定的“${authUser.erp_account}”不一致。` + : '当前云电脑尚未满足执行条件。' + }; + setBridgeState('已连接,有告警', 'state-warn'); + renderStatusDetails(); + return false; } - }); + } catch (heartbeatError) { + erpSessionReady = false; + latestBridgeStatus = { + ...latestBridgeStatus, + erpSessionReady: false, + error: heartbeatError.message || String(heartbeatError) + }; + setBridgeState('已连接,有告警', 'state-warn'); + renderStatusDetails(); + return false; + } } - return bridgeConnected; + return bridgeConnected && erpSessionReady; } catch (error) { bridgeConnected = false; erpAutomationEnabled = false; @@ -5094,6 +5226,7 @@ async function pollTaskResult(taskId, { allowReconciliation = false } = {}) { async function reconcileTaskReceipt(task) { if (!canReconcileTask(task)) throw new Error('当前任务不在可只读回查的生命周期不确定状态。'); + if (!taskAssignedToCurrentAccount(task)) throw new Error('该任务分配给其他平台账号,当前云电脑不能执行回查。'); const bridgeReady = await pingBridge(); if (!bridgeReady || !extensionCompatible || !erpHostAccessReady) throw new Error(`插件未连接、版本低于 ${REQUIRED_EXTENSION_VERSION} 或 ERP 页面权限不可用,未执行回查。`); taskReconciliationStates.set(task.task_id, 'running'); @@ -5112,6 +5245,7 @@ async function reconcileTaskReceipt(task) { async function resumePrewriteTaskExecution(task) { if (!canResumePrewriteTask(task)) throw new Error('当前任务没有可证明的保存前零写状态,禁止恢复。'); + if (!taskAssignedToCurrentAccount(task)) throw new Error('该任务分配给其他平台账号,当前云电脑不能恢复执行。'); const response = await sendToExtension('RESUME_PREWRITE_TASK', { task_id: task.task_id }, 3000); updateLocalTask(task.task_id, { status: 'running', @@ -5128,7 +5262,7 @@ async function pollAllTaskResults() { if (!bridgeConnected || pollInProgress) return; pollInProgress = true; try { - const activeTasks = runtimeTasks().filter(isTaskPollable); + const activeTasks = runtimeTasks().filter((task) => taskAssignedToCurrentAccount(task) && isTaskPollable(task)); for (const task of activeTasks) { try { await pollTaskResult(task.task_id); @@ -5213,6 +5347,7 @@ async function initializeSession() { await syncParserRouting().catch(() => {}); } if (IS_CHANNELS_PAGE && isAdministrator()) { + await syncAccounts().catch(() => {}); await syncChannels().catch((error) => { const message = $('#channelMessage'); if (message) message.textContent = `渠道读取失败:${error.message || String(error)}`; @@ -5306,10 +5441,13 @@ document.addEventListener('DOMContentLoaded', async () => { await syncAutomationSettings().catch(() => setTaskState('全自动化设置读取失败')); } if (IS_PARSER_ROUTING_PAGE && isAdministrator()) await syncParserRouting().catch(() => {}); - if (IS_CHANNELS_PAGE && isAdministrator()) await syncChannels().catch((error) => { - const message = $('#channelMessage'); - if (message) message.textContent = `渠道读取失败:${error.message || String(error)}`; - }); + if (IS_CHANNELS_PAGE && isAdministrator()) { + await syncAccounts().catch(() => {}); + await syncChannels().catch((error) => { + const message = $('#channelMessage'); + if (message) message.textContent = `渠道读取失败:${error.message || String(error)}`; + }); + } if (IS_ACCOUNTS_PAGE && isAdministrator()) await syncAccounts(); if (IS_AUDIT_PAGE && isAdministrator()) await syncAuditEvents(); if (IS_TASK_PAGE) { @@ -5480,7 +5618,19 @@ document.addEventListener('DOMContentLoaded', async () => { $('#accountList')?.addEventListener('change', (event) => { const control = event.target.closest('[data-account-action="role"][data-account-id]'); if (!control) return; - void updateManagedAccount(control.dataset.accountId, { role: control.value }).catch((error) => { + const account = accountList.find((item) => item.id === control.dataset.accountId); + if (!account) return; + const patch = { role: control.value }; + if (control.value === 'admin') patch.erp_account = null; + if (control.value !== 'admin' && !account.erp_account) { + const erpAccount = window.prompt('该员工角色必须绑定 ERP 账号,请输入 ERP 页面显示的登录账号:', ''); + if (!erpAccount?.trim()) { + control.value = account.role; + return; + } + patch.erp_account = erpAccount.trim().slice(0, 200); + } + void updateManagedAccount(control.dataset.accountId, patch).catch((error) => { const message = $('#accountMessage'); if (message) message.textContent = error.message || String(error); void syncAccounts(); @@ -5497,7 +5647,8 @@ document.addEventListener('DOMContentLoaded', async () => { openAccountAuthorizationEditor(account.id); return; } - if (action === 'toggle') operation = updateManagedAccount(account.id, { is_active: !account.is_active }); + if (action === 'erp-account') operation = updateManagedAccountErpIdentity(account.id); + else if (action === 'toggle') operation = updateManagedAccount(account.id, { is_active: !account.is_active }); else if (action === 'reset-password') operation = resetManagedAccountPassword(account.id); else if (action === 'revoke-sessions') operation = revokeManagedAccountSessions(account.id); else return; @@ -5656,6 +5807,22 @@ document.addEventListener('DOMContentLoaded', async () => { if (message) message.textContent = error.message || String(error); }); }); + $('#channelList')?.addEventListener('change', (event) => { + const select = event.target.closest('select[data-channel-action="owner"][data-channel-id]'); + if (!select?.value) return; + void updateChannelOwner(select.dataset.channelId, select.value).catch((error) => { + const message = $('#channelMessage'); + if (message) message.textContent = error.message || String(error); + void syncChannels(); + }); + }); + $('#accountRole')?.addEventListener('change', (event) => { + const erpInput = $('#accountErpAccount'); + const admin = event.target.value === 'admin'; + erpInput.required = !admin; + erpInput.disabled = admin; + if (admin) erpInput.value = ''; + }); $('#aiState').addEventListener('click', () => { toggleStatusDetails('ai').catch(() => {}); }); diff --git a/LianSyn-platform/index.html b/LianSyn-platform/index.html index 3cb35c6..04f6070 100644 --- a/LianSyn-platform/index.html +++ b/LianSyn-platform/index.html @@ -91,13 +91,14 @@

ACCOUNT DIRECTORY

平台账号管理

-

管理员可维护账号、密码与可执行任务类型;未授权指令会在解析和 ERP 执行前被阻止。

+

员工账号必须绑定唯一 ERP 账号;管理员只负责管理和查看,不绑定员工 ERP 执行身份。

+
- + diff --git a/agent设计规范/agentbus-reply-contract.md b/agent设计规范/agentbus-reply-contract.md index 57e0a7c..6808d33 100644 --- a/agent设计规范/agentbus-reply-contract.md +++ b/agent设计规范/agentbus-reply-contract.md @@ -2,6 +2,14 @@ 本契约只约束执行完成后发给 AgentBus 用户的业务回执,不改变 Agent/Skill 的解析 JSON 契约。 +## 归属和执行路由 + +每个 AgentBus 渠道必须一对一绑定一个有效的普通用户或组长平台账号;管理员负责配置和查看,但不能成为员工渠道的归属账号,也不能代替归属账号确认或领取 ERP 执行。渠道只有在归属账号已配置唯一 ERP 账号后才能启用 listener。AgentBus 入站任务在创建时同时固化渠道与 `assigned_user_id`,使用归属账号当时有效的业务类型白名单;不得在领取时按当前在线浏览器、用户名、管理员身份或任意空闲云电脑重新推断归属。 + +员工云电脑必须同时登录该员工的平台账号和平台绑定的 ERP 账号并打开扩展。同一平台账号在 90 秒心跳新鲜期内只允许一个执行 worker;扩展只回传期望 ERP 账号是否匹配,控制面不接收页面中的其他账号文本。领取任务和提交结果都会重新校验任务归属、worker 连接与 ERP 身份。任一项缺失、过期、冲突或不匹配都失败关闭,不把任务转交给管理员或其他在线员工。 + +旧未绑定渠道默认停用,历史上无法可靠推断归属的 AgentBus 任务保持未分配且不得自动执行。渠道删除后,历史任务保留原 `assigned_user_id`,该渠道尚未投递的回执随渠道删除且不得改投其他渠道。 + ## 用户侧消息 用户侧只收到必要的阶段消息: diff --git a/agent设计规范/business-adaptation-registry.md b/agent设计规范/business-adaptation-registry.md index f96a3bb..143557c 100644 --- a/agent设计规范/business-adaptation-registry.md +++ b/agent设计规范/business-adaptation-registry.md @@ -1,6 +1,6 @@ # 业务适配登记表 -这是跨会话和交付的业务入口。它回答三个问题:业务人员怎么输入、ERP 怎么执行、Skill 在哪里维护。运营统一复制入口见[运营业务 AI 输入模板总表](templates/business-input-templates.md),AI 业务路由参考见 [business-behavior-registry.md](business-behavior-registry.md),18 项确定性解析的指令、字段、action 和版本唯一机器源见 [`control-plane/src/business-routes.ts`](../control-plane/src/business-routes.ts)。该机器路由、任务级解析模式快照和组织全自动化规则对手工与 AgentBus 新任务全局共用,来源只负责输入和回执适配。 +这是跨会话和交付的业务入口。它回答三个问题:业务人员怎么输入、ERP 怎么执行、Skill 在哪里维护。运营统一复制入口见[运营业务 AI 输入模板总表](templates/business-input-templates.md),AI 业务路由参考见 [business-behavior-registry.md](business-behavior-registry.md),18 项确定性解析的指令、字段、action 和版本唯一机器源见 [`control-plane/src/business-routes.ts`](../control-plane/src/business-routes.ts)。该机器路由、任务级解析模式快照和组织全自动化规则对手工与 AgentBus 新任务全局共用,来源只负责输入和回执适配;AgentBus 渠道必须绑定一个员工平台账号,并使用该账号的业务白名单、任务归属、唯一云电脑 worker 和 ERP 身份门禁。 ## 状态定义 diff --git a/agent设计规范/test-fixtures/lwlt-lifecycle/release-gate.md b/agent设计规范/test-fixtures/lwlt-lifecycle/release-gate.md index 661de18..9dbc77c 100644 --- a/agent设计规范/test-fixtures/lwlt-lifecycle/release-gate.md +++ b/agent设计规范/test-fixtures/lwlt-lifecycle/release-gate.md @@ -2,11 +2,11 @@ ## 当前基线 -- Chrome 插件:`0.5.163` +- Chrome 插件:`0.5.164` - Agent Prompt:`ltjt-agent-prompt-v1.8-independent-headcount-categories` - 生命周期契约:`ltjt-lifecycle-v2.9-roster-leader-contact-2026-08` - 五个 Skill:`0.5.125`;运营 DOCX:`0.5.125` -- 发布状态:核心窄能力已有真实证据;一笔名单附件 Program-only 任务已把 25 行及唯一领队联系人正确保存到 ERP,后续只读逐字段核验全等。两种写后逐行读取方式都曾在真实成功写入后产生假阴性;名单在全部写前门禁通过后,以父表单 ERP 明确成功响应作为终结证据,不再执行写后逐行回查或自动名单对账。`0.5.155` 规定确认覆盖的指定序号同值也写;`0.5.156` 把原生游客位不足统一映射为包含名单人数和 ERP 实际上限的业务提示,原技术 blocker 仅留技术详情。`0.5.157` 新增散拼母团“整团游客信息”的 `shared_plan + visitor-list + tid-only` 窄分支,静态契约与回归已通过,仍待授权后的新版运行态只读复测。`0.5.158` 把正式控制面 `https://lwlt.nianxx.cn` 同步加入 Popup、后台、Manifest 权限和 content script 精确白名单;其他 HTTPS origin 仍保持阻断。`0.5.159` 修复 MV3 后台恢复被中断后持久化 `running` 永久阻断插件派发的问题:仅在内存恢复 Promise 已不存在时收敛为 `interrupted`,真实活动恢复仍保持阻断,下一次成功只读保活把状态归零为 `idle`。`0.5.160` 为独立团单个下单写前漂移增加仅字段名诊断,并把数字后缀动态应收字段纳入保护。`0.5.161` 等待客户、跟单人和销售人原生 lookup 数据就绪后才开始单团预检;生产反馈证明仅检查 lookup 数据仍不足。`0.5.162` 进一步要求页面 jQuery Ajax 在预检前归零,并在 `GetProduct` 后等待原生 `ajaxStop` 再应用最终业务字段。`0.5.163` 按生产操作方明确要求恢复 `0.5.156` 的实际校验范围:核心订单字段继续写前阻断,带编号的动态 `ys_*` 应收行不再纳入保护哈希,允许 ERP 原生回调在预检后调整这些字段。新增独立团房型/人数映射仍待实写;未验证宽能力继续失败关闭。 +- 发布状态:核心窄能力已有真实证据;一笔名单附件 Program-only 任务已把 25 行及唯一领队联系人正确保存到 ERP,后续只读逐字段核验全等。两种写后逐行读取方式都曾在真实成功写入后产生假阴性;名单在全部写前门禁通过后,以父表单 ERP 明确成功响应作为终结证据,不再执行写后逐行回查或自动名单对账。`0.5.155` 规定确认覆盖的指定序号同值也写;`0.5.156` 把原生游客位不足统一映射为包含名单人数和 ERP 实际上限的业务提示,原技术 blocker 仅留技术详情。`0.5.157` 新增散拼母团“整团游客信息”的 `shared_plan + visitor-list + tid-only` 窄分支,静态契约与回归已通过,仍待授权后的新版运行态只读复测。`0.5.158` 把正式控制面 `https://lwlt.nianxx.cn` 同步加入 Popup、后台、Manifest 权限和 content script 精确白名单;其他 HTTPS origin 仍保持阻断。`0.5.159` 修复 MV3 后台恢复被中断后持久化 `running` 永久阻断插件派发的问题:仅在内存恢复 Promise 已不存在时收敛为 `interrupted`,真实活动恢复仍保持阻断,下一次成功只读保活把状态归零为 `idle`。`0.5.160` 为独立团单个下单写前漂移增加仅字段名诊断,并把数字后缀动态应收字段纳入保护。`0.5.161` 等待客户、跟单人和销售人原生 lookup 数据就绪后才开始单团预检;生产反馈证明仅检查 lookup 数据仍不足。`0.5.162` 进一步要求页面 jQuery Ajax 在预检前归零,并在 `GetProduct` 后等待原生 `ajaxStop` 再应用最终业务字段。`0.5.163` 按生产操作方明确要求恢复 `0.5.156` 的实际校验范围:核心订单字段继续写前阻断,带编号的动态 `ys_*` 应收行不再纳入保护哈希,允许 ERP 原生回调在预检后调整这些字段。`0.5.164` 新增平台账号、AgentBus 渠道、唯一云电脑 worker 与 ERP 登录身份四方一致门禁;旧未绑定渠道和历史未归属 AgentBus 任务默认不执行。新增独立团房型/人数映射仍待实写;未验证宽能力继续失败关闭。 运行中的浏览器版本必须实时握手确认。当前制品文件名和 SHA-256 只看 [`../../../dist/release-manifest.json`](../../../dist/release-manifest.json),不从历史日志推断。 diff --git a/archive/releases/2026-09-02/README.md b/archive/releases/2026-09-02/README.md new file mode 100644 index 0000000..f34bff3 --- /dev/null +++ b/archive/releases/2026-09-02/README.md @@ -0,0 +1,6 @@ +# 2026-09-02 发布归档 + +本目录只保存被新版本替代的历史发布物,不定义当前运行规则。当前制品文件名和 SHA-256 只以根目录 [`../../../dist/release-manifest.json`](../../../dist/release-manifest.json) 为准。 + +- `release-manifest-0.5.163.json`:引入账号、渠道、云电脑 worker 与 ERP 身份一致性门禁前的历史清单快照。 +- `ltjt-order-assistant-0.5.163.zip`:被 `0.5.164` 的员工账号路由和 ERP 身份核验替代;保留用于恢复和追溯。 diff --git a/dist/ltjt-order-assistant-0.5.163.zip b/archive/releases/2026-09-02/ltjt-order-assistant-0.5.163.zip similarity index 100% rename from dist/ltjt-order-assistant-0.5.163.zip rename to archive/releases/2026-09-02/ltjt-order-assistant-0.5.163.zip diff --git a/archive/releases/2026-09-02/release-manifest-0.5.163.json b/archive/releases/2026-09-02/release-manifest-0.5.163.json new file mode 100644 index 0000000..bc363a5 --- /dev/null +++ b/archive/releases/2026-09-02/release-manifest-0.5.163.json @@ -0,0 +1,69 @@ +{ + "manifest_version": 1, + "generated_on": "2026-09-01", + "baselines": { + "chrome_extension": "0.5.163", + "skills": "0.5.125", + "business_instruction_docx": "0.5.125", + "agent_prompt": "ltjt-agent-prompt-v1.8-independent-headcount-categories" + }, + "artifacts": [ + { + "kind": "chrome_extension", + "version": "0.5.163", + "path": "dist/ltjt-order-assistant-0.5.163.zip", + "source": "chrome-extension/ltjt-order-assistant", + "sha256": "90c550054cdf747aa9c2c80bca5ee5fba0d13f126acfd4779f85400764379bf0" + }, + { + "kind": "skill", + "name": "lwlt-arrangement", + "version": "0.5.125", + "path": "dist/lwlt-arrangement-0.5.125.skill", + "source": "agent设计规范/skills/lwlt-arrangement", + "sha256": "c729bebec84695b8642a6d31cb4dc0d963135b6c6620c2173be095826cd3976d" + }, + { + "kind": "skill", + "name": "lwlt-confirmation", + "version": "0.5.125", + "path": "dist/lwlt-confirmation-0.5.125.skill", + "source": "agent设计规范/skills/lwlt-confirmation", + "sha256": "659f6aca7918a03a29fc78f0b4b938dfa9084ccdbb44ebd71f6399556cfa4609" + }, + { + "kind": "skill", + "name": "lwlt-lifecycle", + "version": "0.5.125", + "path": "dist/lwlt-lifecycle-0.5.125.skill", + "source": "agent设计规范/skills/lwlt-lifecycle", + "sha256": "d964c0a9e482f82757fea25d4713f0c81feb2704486583dfa84f2336ecd930c0" + }, + { + "kind": "skill", + "name": "lwlt-newbooking", + "version": "0.5.125", + "path": "dist/lwlt-newbooking-0.5.125.skill", + "source": "agent设计规范/skills/lwlt-newbooking", + "sha256": "bb610c50dd659fc172aabfa469b3028de98704c249cfd4890463deea0c88cd0c" + }, + { + "kind": "skill", + "name": "lwlt-updating", + "version": "0.5.125", + "path": "dist/lwlt-updating-0.5.125.skill", + "source": "agent设计规范/skills/lwlt-updating", + "sha256": "c6f5bc9008e2f63d170f3f140140003d99411ce5232632c48b38891da1ceee33" + }, + { + "kind": "business_instruction_docx", + "version": "0.5.125", + "path": "dist/老挝联泰AI指令表-0.5.125.docx", + "source": "agent设计规范/templates/business-input-templates.md", + "builder": "tools/build_business_instruction_docx.py", + "sha256": "a0c2fd5f808e3d3f2ac211a652a59f2e435d3824df49c51f763ca4beb7d9b97d", + "source_sha256": "27600fe9bcb0de3898a99550428a9890dd350c701ba704fb3940ca966088dcd9", + "builder_sha256": "c162884210da22e3b78368749b66f1f177df5e9fc6155f4fd9954bd516d187f1" + } + ] +} diff --git a/chrome-extension/ltjt-order-assistant/README.md b/chrome-extension/ltjt-order-assistant/README.md index 779374f..7515c86 100644 --- a/chrome-extension/ltjt-order-assistant/README.md +++ b/chrome-extension/ltjt-order-assistant/README.md @@ -11,9 +11,12 @@ Chrome Manifest V3 扩展,在用户已登录的 LTJT ERP 页面内执行经过 ## 当前版本 -当前源码版本为 `0.5.163`。版本化 ZIP、文件哈希和 Skill/DOCX 基线见 [`../../dist/release-manifest.json`](../../dist/release-manifest.json)。旧版本实现流水已冻结在 [`../../archive/project-history/2026-08-16/chrome-extension-README.pre-governance.md`](../../archive/project-history/2026-08-16/chrome-extension-README.pre-governance.md)。 +当前源码版本为 `0.5.164`。版本化 ZIP、文件哈希和 Skill/DOCX 基线见 [`../../dist/release-manifest.json`](../../dist/release-manifest.json)。旧版本实现流水已冻结在 [`../../archive/project-history/2026-08-16/chrome-extension-README.pre-governance.md`](../../archive/project-history/2026-08-16/chrome-extension-README.pre-governance.md)。 -0.5.163 当前重点: +0.5.164 当前重点: + +- 平台会把当前员工账号绑定的 ERP 账号随只读 PING 交给扩展核验;扩展只返回是否匹配,不回传页面中的其他账号文本。 +- 只有平台确认的唯一在线云电脑、匹配的 ERP 登录身份和任务归属账号三者一致时,任务才可领取或回传结果。 - 保留 `0.5.162` 的稳定态处理:独立团单个下单只有在表单结构、客户/跟单人/销售人三个原生 SelectBox lookup 数据以及页面 jQuery Ajax 全部就绪后才进入预检;`GetProduct` 外层完成后还会等待页面自身 `ajaxStop`,再应用最终业务字段。该门禁使用原生完成事件和失败超时,不使用固定 sleep,也不在 live gate 静默回填。 diff --git a/chrome-extension/ltjt-order-assistant/background.js b/chrome-extension/ltjt-order-assistant/background.js index 98426fd..07b7dac 100644 --- a/chrome-extension/ltjt-order-assistant/background.js +++ b/chrome-extension/ltjt-order-assistant/background.js @@ -1347,7 +1347,8 @@ async function findOrOpenErpTab(taskId = '') { } } -async function readErpSessionStatus() { +async function readErpSessionStatus(expectedErpAccount = '') { + const normalizedExpectedAccount = String(expectedErpAccount || '').trim(); const permission = await erpHostPermissionStatus(); const keepalive = await readErpKeepaliveState(); const tabs = await chrome.tabs.query({ url: `${ERP_ORIGIN}/*` }); @@ -1356,6 +1357,7 @@ async function readErpSessionStatus() { return { ok: false, tab_present: false, + account_configured: Boolean(normalizedExpectedAccount), account_matched: false, url: '', session_ready: null, @@ -1372,6 +1374,7 @@ async function readErpSessionStatus() { return { ok: false, tab_present: true, + account_configured: Boolean(normalizedExpectedAccount), account_matched: false, url: tab.url || '', session_ready: false, @@ -1385,16 +1388,18 @@ async function readErpSessionStatus() { try { const [inspection] = await executeErpScript(tab.id, { target: { tabId: tab.id }, - func: () => { + func: (expectedAccount) => { const bodyText = document.body?.innerText || ''; return { url: location.href, title: document.title, - account_matched: bodyText.includes('测试ai员工账号'), + account_configured: Boolean(expectedAccount), + account_matched: Boolean(expectedAccount) && bodyText.includes(expectedAccount), login_or_permission_error: Boolean(document.querySelector('input[type="password"]')) || /login|无权限|权限不足|permission denied/i.test(`${location.href}\n${document.title}\n${bodyText.slice(0, 400)}`) }; - } + }, + args: [normalizedExpectedAccount] }); const inspectionResult = inspection?.result || { url: tab.url || '', @@ -3753,7 +3758,7 @@ chrome.runtime.onConnect.addListener((port) => { chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { if (message?.type === 'LTJT_ERP_SESSION_STATUS') { - readErpSessionStatus() + readErpSessionStatus(message.expected_erp_account || '') .then((result) => sendResponse(result)) .catch((error) => sendResponse({ ok: false, diff --git a/chrome-extension/ltjt-order-assistant/business-bridge.js b/chrome-extension/ltjt-order-assistant/business-bridge.js index 42c1dae..c3873b3 100644 --- a/chrome-extension/ltjt-order-assistant/business-bridge.js +++ b/chrome-extension/ltjt-order-assistant/business-bridge.js @@ -45,7 +45,7 @@ async function getAutomationState() { }; } -async function bridgePayload(extra = {}) { +async function bridgePayload(extra = {}, expectedErpAccount = '') { let erpSession = { ok: false, tab_present: false, @@ -53,7 +53,10 @@ async function bridgePayload(extra = {}) { url: '' }; try { - erpSession = await chrome.runtime.sendMessage({ type: 'LTJT_ERP_SESSION_STATUS' }) || erpSession; + erpSession = await chrome.runtime.sendMessage({ + type: 'LTJT_ERP_SESSION_STATUS', + expected_erp_account: String(expectedErpAccount || '').trim() + }) || erpSession; } catch (error) { erpSession = { ...erpSession, message: error.message || String(error) }; } @@ -267,7 +270,7 @@ const bridgeHandler = async (event) => { const requestId = message.requestId || ''; try { if (message.type === 'PING') { - postReply(requestId, 'PONG', await bridgePayload()); + postReply(requestId, 'PONG', await bridgePayload({}, message.payload?.expected_erp_account)); return; } if (message.type === 'CREATE_TASK') { diff --git a/chrome-extension/ltjt-order-assistant/inpage.js b/chrome-extension/ltjt-order-assistant/inpage.js index 570f13d..5978c26 100644 --- a/chrome-extension/ltjt-order-assistant/inpage.js +++ b/chrome-extension/ltjt-order-assistant/inpage.js @@ -6077,7 +6077,7 @@ } window.LTJTOrderAssistant = { - version: '0.5.163', + version: '0.5.164', resolveNativeListSearchValues, lookupKeywordMatchesText, inspectLifecycleSearchCriteria: lifecycleSearchCriteria, diff --git a/chrome-extension/ltjt-order-assistant/manifest.json b/chrome-extension/ltjt-order-assistant/manifest.json index a5a80c5..181ccce 100644 --- a/chrome-extension/ltjt-order-assistant/manifest.json +++ b/chrome-extension/ltjt-order-assistant/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "联泰下单助手", - "version": "0.5.163", + "version": "0.5.164", "description": "在已登录 LTJT ERP 页面内规划并受控执行联泰 ERP 业务操作。", "permissions": [ "activeTab", diff --git a/chrome-extension/ltjt-order-assistant/team-batch-inpage.js b/chrome-extension/ltjt-order-assistant/team-batch-inpage.js index ebc9030..d485b3c 100644 --- a/chrome-extension/ltjt-order-assistant/team-batch-inpage.js +++ b/chrome-extension/ltjt-order-assistant/team-batch-inpage.js @@ -919,7 +919,7 @@ window.LTJTOrderAssistant = { ...(window.LTJTOrderAssistant || {}), - version: '0.5.163', + version: '0.5.164', openTeamBatchForm, pingTeamBatchFrame, preflightTeamBatchNative, diff --git a/control-plane/README.md b/control-plane/README.md index 58e72a3..9562b44 100644 --- a/control-plane/README.md +++ b/control-plane/README.md @@ -7,27 +7,27 @@ - PostgreSQL 是任务、事件、幂等、审计和回查状态的唯一事实源。 - 平台账号使用 `admin`(管理员)、`team_lead`(组长)和 `user`(普通用户)三种固定角色登录;服务端会话使用 HttpOnly/Secure/SameSite Cookie。管理员维护账号、角色、状态、密码重置、会话撤销和可执行任务类型;平台不提供组织或租户选择。 - 登录会话默认跨浏览器重启持续有效,不按空闲时间或绝对时长自动失效;用户退出、修改密码、账号停用或管理员强制撤销时由服务端立即撤销。 -- 业务页面通过 REST 创建任务;Agent 返回结构化结果后,任务创建人在自己的任务上一次点击“确认并提交到 ERP 插件”,再通过 SSE 或轮询读取服务端状态。普通用户与组长的正常任务 API、SSE、附件和插件执行权限都只覆盖本人创建的人工任务;管理员可访问固定部署范围内的全部任务和 AgentBus/system 任务。 +- 业务页面通过 REST 创建任务;Agent 返回结构化结果后,任务归属人在自己的任务上一次点击“确认并提交到 ERP 插件”,再通过 SSE 或轮询读取服务端状态。普通用户与组长的任务 API、SSE、附件和插件执行权限覆盖分配给本人的人工任务与 AgentBus 任务;管理员可查看固定部署范围内的全部任务,但确认、插件领取和执行回执仍必须来自任务归属账号,查看权限不等于执行权限。 - 手工与 AgentBus 的每个新任务都会按同一组织、同一 18 项业务路由固化解析策略及配置 revision;来源不能覆盖模式。其中两项名单 route 固定为 `program_only`,其余 16 项可配置 `ai / shadow / auto / program`。全消息中的唯一已登记指令可以确定 route;没有指令时,只有全部标签都属于唯一 route、至少两个不同标签且业务定位必填项完整的字段签名才可确定 route。未知、冲突或多 route 输入不猜测。后续补充轮次沿用原任务快照,设置变化只影响新任务和新会话。 -- 管理员天然拥有全部 18 类人工业务。组长和普通用户使用逐账号白名单,新账号默认没有任何可执行任务类型;管理员在 `/accounts` 逐项授权后才能提交对应的新任务、补充指令或名单附件。已登记但未授权的业务返回 `business_not_authorized`;无法唯一确认 route 的非管理员人工输入返回 `business_type_unresolved`。两种拒绝都发生在解析器和 ERP 插件之前,并记录不含明文的授权拒绝审计。权限在补充输入、人工确认、全自动确认和插件领取前再次校验;取消授权后的任务不会进入 ERP 队列。AgentBus 继续使用管理员控制的独立渠道边界,不映射为平台普通账号权限。 +- 管理员天然拥有全部 18 类人工业务。组长和普通用户使用逐账号白名单,新账号默认没有任何可执行任务类型;管理员在 `/accounts` 逐项授权后才能提交对应的新任务、补充指令或名单附件。已登记但未授权的业务返回 `business_not_authorized`;无法唯一确认 route 的非管理员输入返回 `business_type_unresolved`。两种拒绝都发生在解析器和 ERP 插件之前,并记录不含明文的授权拒绝审计。权限在补充输入、人工确认、全自动确认和插件领取前再次校验;取消授权后的任务不会进入 ERP 队列。AgentBus 入站使用渠道绑定员工的同一白名单,管理员账号不得成为员工渠道归属人。 - 名单 route 创建后先进入 `awaiting_attachment`,只接收一份 `.xls/.xlsx`;控制面在前 100 行中自动定位唯一的 ERP 名单字段表头,按精确字段语义映射任意表头行、列顺序及受支持别名,并把表头下方连续名单数据在内存中失败关闭地规范化为 13 列 canonical TSV。未知额外列、重复语义字段和多个候选表头继续阻断。原始工作簿不入库,文件名和 canonical TSV 使用字段加密,解析通过或终止后清除 canonical 中间文本。附件到齐前不会领取解析任务,也不会进入 ERP。独立团初始 16 行、散拼子单初始 31 行仅为 ERP 动态扩行基线,5000 为技术上限。 - 平台的任务 ID、Agent 会话 ID、确认状态、重要摘要、事件和用户通讯内容只存在于平台任务/会话/结果信封中,不回写到 Agent `operation`,也不成为 ERP 业务字段。 - Agent `operation` 只保存解析态业务事实。插件领取已确认任务后先做无需 ERP 查询的前门禁,再在 ERP 内只读唯一解析对象、资源和当前状态,最后对内部 execution operation 执行严格写前门禁。 -- Chrome 插件仍在当前平台账号已登录的浏览器/ERP 会话中工作;浏览器连接绑定该平台账号,`chrome.storage.local` 只是临时缓存。 +- Chrome 插件仍在当前平台账号已登录的浏览器/ERP 会话中工作;每个员工平台账号必须配置唯一 ERP 账号,同一平台账号在 90 秒新鲜期内只允许一台云电脑保持执行 worker。心跳携带扩展对期望 ERP 账号的只读匹配结果;账号不匹配、心跳过期、第二台并发云电脑或任务归属不一致都会在领取和回执两端失败关闭。`chrome.storage.local` 只是临时缓存。 - `confirmation_export` 先按对象范围选择 ERP 源:独立团/散拼具体子单的游客名单使用 `did+tid`,散拼母团的整团游客信息只使用 `tid`,且母团只开放这一单一文件类型。附件在 `TaskArtifactStore` 写入前按类型处理:游客信息保留原始 ERP `.xls` 作为源归档,同时由平台通过 LibreOffice 生成真实 `.xlsx`;AgentBus/微信只投递 `.xlsx`,若 XLSX 转换失败则阻断外部附件交付。其他类型由平台转换为 PDF,转换失败回退对应 ERP 源文件。生产附件写入 OSS;PostgreSQL 只保存任务归属、文件元数据和 OSS object key。OSS Bucket 按当前生产策略公共可读,但只有后端凭据可写/删;对象 URL 使用随机执行 UUID,不包含客户名或团号。 -- ERP 写入继续遵循预检、任务创建人确认(或管理员配置的全自动化)、单次提交、ERP 回查;不确定结果禁止自动重试。 -- 由于插件不使用独立凭据,执行期间必须保持任务创建人的业务页面和已登录 ERP 浏览器会话可用;页面断开不会触发自动补偿或重复提交。 +- ERP 写入继续遵循预检、任务归属人确认(或管理员配置的全自动化)、单次提交、ERP 回查;不确定结果禁止自动重试。 +- 由于插件不使用独立凭据,执行期间必须保持任务归属人的业务页面和已登录 ERP 浏览器会话可用;页面断开不会触发自动补偿或重复提交。 - 服务端先创建唯一 ERP execution,再允许页面向插件下发;同一任务只有一个 `erp` attempt。刷新、重连、超时和迟到回执都不能创建第二次执行。 - 插件回执必须携带服务端 `execution_id` 和领取连接;完成、阻断及待回查状态不可被后续 `running` 回执覆盖。执行租约过期会进入待回查,不会重新入队。 - Chrome 插件最低兼容版本由正式操作台与根目录发布清单共同门禁。插件包含分段前门禁、ERP 只读唯一解析、严格写前门禁、当前窄生命周期适配、写入前 `write_started` 持久化和写后回查;扩展后台重启后也不会重跑同一任务。本次双轨解析不修改插件执行契约或扩展版本。 - 渠道 Adapter 由 AgentBus 负责;控制平面只作为 AgentBus Bot 连接到文档中的 WebSocket,不实现微信、个人微信或其他渠道协议。 - 微信桥接器把正文放在严格的 `New WeChat message` / `Conversation:` / `Text:` 三行传输信封中;AgentBus listener 会在任务快照前只解开这一已知信封,把 `Text:` 同行值及后续行作为业务正文,并在帧没有显式 `conversation_id` 时使用信封中的 `Conversation:` 值。显式字段仍优先;近似、缺失字段或空正文的包装保持原文,不能通过忽略任意未知标签来绕过 Program parser 的失败关闭。 - 微信侧的 `[WeChat attachment: 文件名]` 只是一段传输占位文字,不代表控制面已经收到文件。若同一帧没有符合契约的 `payload.attachments[]`,listener 会在进入任务服务前失败关闭、保留原名单任务的等待状态,并返回“附件内容未传到平台”;不会把占位文字创建成新业务任务。附件元数据、HTTPS URL、DNS、大小或摘要校验失败时返回对应的安全摘要,仍不回显 URL、文件字节或名单内容。当前生产部署位于受信内网,入站附件 URL 可以使用内网域名、私网 IPv4/IPv6 或 localhost;因此 AgentBus 渠道和上游桥接器必须被视为受信输入边界。 -- AgentBus 入站消息会复用 `TaskService` 的任务/会话/解析队列,解析完成后通过同一 WebSocket 返回一次 `task.result`。组织级“全自动化”关闭时,手工与 AgentBus 新任务都要求管理员确认;开启后,两种来源的合法解析结果都自动进入 ERP 队列,不再按来源或创建、名单、安排、修改、取消/恢复、导出等业务类型保留人工例外。操作台在 EventSource 建连/重连、30 秒后台刷新以及页面重新可见或聚焦时重新读取数据库权威开关,避免后台变更后按钮仍显示旧值。缺资料、解析失败、歧义、插件校验失败或 ERP 回查不确定时仍会停止,不会绕过校验或重试不确定写入。 -- 单一部署范围可以维护多个“用户渠道”。渠道代表外部 AgentBus 用户身份,不等同于平台账号;管理员在独立根路径 `/channels` 的“AgentBus 渠道”目录中创建、停用、启用、轮换或删除渠道。删除会停止对应 listener、移除服务端保存的 key 和该渠道尚存的持久化回执;历史任务本体保留,其 `channel_id` 按数据库契约置空。每个渠道独立保存加密后的 AgentBus key,并建立独立 WebSocket listener;列表和日志都不会回显 key。`AGENTBUS_WS_URL`、重连策略和客户端类型仍是全局连接配置,`AGENTBUS_BOT_ADDRESS` 可作为渠道 bot address 的默认值。仍由完整旧环境变量托管的兼容渠道会自动重建,必须先移除环境配置并重启服务,才允许删除其数据库记录。 +- AgentBus 入站消息会复用 `TaskService` 的任务/会话/解析队列,并以渠道绑定员工写入 `created_by` 与不可变的 `assigned_user_id`,解析完成后通过同一 WebSocket 返回一次 `task.result`。组织级“全自动化”关闭时,手工与 AgentBus 新任务都需要人工确认;开启后,两种来源的合法解析结果都自动进入 ERP 队列,不再按来源或创建、名单、安排、修改、取消/恢复、导出等业务类型保留人工例外。历史未归属 AgentBus 任务不会自动执行。操作台在 EventSource 建连/重连、30 秒后台刷新以及页面重新可见或聚焦时重新读取数据库权威开关。缺资料、解析失败、歧义、插件校验失败或 ERP 回查不确定时仍会停止,不会绕过校验或重试不确定写入。 +- 单一部署范围可以维护多个“用户渠道”。每个渠道代表一个外部 AgentBus 用户身份,并且必须一对一绑定一个有效的非管理员平台账号;一个平台账号也只能绑定一个渠道。管理员在 `/channels` 创建、绑定、停用、启用、轮换或删除渠道。只有绑定账号有效且已配置 ERP 账号的启用渠道才启动 listener;未绑定渠道失败关闭。删除会停止对应 listener、移除服务端保存的 key 和该渠道尚存的持久化回执;历史任务本体保留,其 `channel_id` 置空而 `assigned_user_id` 不变。每个渠道独立保存加密后的 AgentBus key,同一 key 不能被多个渠道复用;列表和日志都不会回显 key。`AGENTBUS_WS_URL`、重连策略和客户端类型仍是全局连接配置,`AGENTBUS_BOT_ADDRESS` 可作为渠道 bot address 的默认值。 - `/history` 使用可恢复的归档/恢复,不提供物理删除。单条兼容路由 `DELETE /api/tasks/:taskId` 与批量兼容路由 `POST /api/tasks/bulk-delete` 也只执行归档;普通用户和组长只能归档/恢复本人任务,管理员可以处理全部授权任务。任务、输入、事件、尝试、附件元数据与审计记录继续保留,物理清除必须等待单独批准的保留期限和不可逆清除设计。 - `/operations-dashboard` 是组长和管理员专用的只读业务操作看板。它支持从结果状态、操作人、上海业务日期和业务类型逐层穿透,并可在选定范围内查询姓名、完整初始/补充指令、业务结果、团号或订单号。列表和详情只回答“谁提交了什么指令、完成了什么结果”:详情返回操作人、业务类型、完整指令轮次、输入附件名称/行数和可读业务结果,不返回任务生命周期、解析/执行 JSON、技术阶段、错误码或产物地址。关键词查询先受日期、人员、业务和状态约束,单次解密匹配候选最多 2,000 条,超过时要求继续缩小范围。该路径不授予他人任务修改、ERP 执行、SSE、产物下载、账号维护或全局安全审计权限,并排除 AgentBus/system 任务。 -- 使用数据库渠道时设置 `AGENTBUS_ENABLED=true`;此模式不要求 `AGENTBUS_WS_TOKEN` 或 `AGENTBUS_BOT_ADDRESS`,但启用的渠道仍需要全局 `AGENTBUS_WS_URL`,并可在渠道上覆盖 bot address。保留旧环境变量配置时,服务会按需创建“默认 AgentBus 渠道”兼容旧单渠道部署;`AGENTBUS_ENABLED=auto` 仅由完整的旧环境连接字段自动启用。 +- 使用数据库渠道时设置 `AGENTBUS_ENABLED=true`;此模式不要求 `AGENTBUS_WS_TOKEN` 或 `AGENTBUS_BOT_ADDRESS`,但启用的渠道仍需要全局 `AGENTBUS_WS_URL`,并可在渠道上覆盖 bot address。保留旧环境变量配置时,服务会按需创建“默认 AgentBus 渠道”兼容旧单渠道部署;兼容渠道初始为未绑定且不启动,管理员必须在 `/channels` 绑定员工账号后再启用。`AGENTBUS_ENABLED=auto` 仅由完整的旧环境连接字段自动启用。 - `user_channels`、`tasks.channel_id` 和 `agentbus_deliveries` 共同保存入站归属、accepted 受理回执和最终 result 回执。回执以 `(channel_id, inbound_frame_id, delivery_kind)` 幂等,发送失败会重试,进程重启或 WebSocket 重连后仍会继续投递;因此不会因为超过原等待时长而丢掉最终回复。 - ERP 插件领取由组织级数据库锁和 FIFO confirmed 队列统一串行化:同一组织/同一 ERP 浏览器会话在任意时刻最多一个 ERP execution,其他任务留在服务端等待;已开始写入但结果不确定的任务会阻塞后续领取,直到人工回查收敛。 @@ -89,11 +89,13 @@ Auto 一旦发生 AI fallback,任务会永久绑定原 AI 会话。每次解 - `POST /api/tasks/:taskId/reparse` - `PUT /api/parser-decisions/:decisionId/review` -迁移 `013_business_parser_modes` 增加内部固定范围的路由设置、任务快照和加密的 `parse_decisions`;迁移 `014_task_input_attachments` 增加名单输入附件元数据、加密 canonical TSV 与 `awaiting_attachment` 索引;迁移 `015_account_roles_and_task_audit` 增加账号角色、密码更新时间、输入/附件操作者、任务归档和账号级幂等(历史 `must_change_password` 列仅保留兼容,当前流程不启用首次强制改密);迁移 `016_team_lead_operations_dashboard` 增加组长角色与人工指令看板索引;迁移 `017_user_business_route_authorizations` 增加逐账号业务白名单、授权人和乐观并发 revision。原文、完整程序/AI 候选、名单 canonical 中间文本和人工说明使用字段加密保存;统计、全局审计和运行日志不复制明文业务输入。 +迁移 `013_business_parser_modes` 增加内部固定范围的路由设置、任务快照和加密的 `parse_decisions`;迁移 `014_task_input_attachments` 增加名单输入附件元数据、加密 canonical TSV 与 `awaiting_attachment` 索引;迁移 `015_account_roles_and_task_audit` 增加账号角色、密码更新时间、输入/附件操作者、任务归档和账号级幂等(历史 `must_change_password` 列仅保留兼容,当前流程不启用首次强制改密);迁移 `016_team_lead_operations_dashboard` 增加组长角色与人工指令看板索引;迁移 `017_user_business_route_authorizations` 增加逐账号业务白名单、授权人和乐观并发 revision;迁移 `018_agentbus_account_workers` 增加 ERP 账号、渠道归属、任务执行归属、唯一在线 worker 与 ERP 身份核验字段。原文、完整程序/AI 候选、名单 canonical 中间文本和人工说明使用字段加密保存;统计、全局审计和运行日志不复制明文业务输入。 ## AgentBus Bot 接入 -将 onboarding 文档中的 WebSocket 地址、WebSocket Token、Bot Address 和 Worker Address 写入服务端受保护的环境文件。配置 `AGENTBUS_ENABLED=auto` 时,只要填写 AgentBus 连接字段,控制平面就会自动启动长期监听;保持这些字段为空则不启动监听。 +数据库渠道模式在服务端受保护的环境文件中配置全局 `AGENTBUS_WS_URL` 并设置 `AGENTBUS_ENABLED=true`。管理员先在 `/accounts` 为员工配置唯一 ERP 账号和允许的业务类型,再在 `/channels` 创建渠道、填写该渠道自己的 AgentBus key,并选择对应员工账号。员工随后在自己的云电脑同时登录该平台账号与所绑定 ERP 账号并打开扩展。只有这四项就绪的启用渠道才启动长期 listener。 + +旧单渠道环境变量仍可创建一个兼容渠道记录,但该记录初始未绑定、停用且不启动;管理员必须完成员工绑定后手动启用。`AGENTBUS_ENABLED=auto` 只用于识别完整的旧环境连接字段,不会绕过账号绑定。 每个启用渠道会连接 `AGENTBUS_WS_URL?ready=1`,使用该渠道自己的 `Authorization: Bearer `,等待 `session.ready` 后接收普通 `event` 消息。普通任务发送一次持久化受理通知(`task.progress`,`status=accepted`)并在完成时返回一次 `task.result`。名单任务的首次文字指令改为返回明确的等待附件提示,附件入站改为返回“名单附件已收到,正在校验并处理”;最终结果只归属触发解析的最新附件消息,因此不会因文字与附件两条入站帧重复发送成功回执。解析完成、等待确认或进入 ERP 等内部进度不外发。`GET /health/ready` 和 `GET /api/status` 的 `agentbus.channels` 字段可用于确认每个 listener 与 session 是否建立。 @@ -139,7 +141,7 @@ npm run data:retention npm run dev ``` -`npm run dev` 和 `npm start` 会先执行数据库迁移,再启动控制平面;直接运行 `control-plane/src/server.ts` 或构建后的 `server.js` 时,服务也会在启动前检查必需迁移 `017_user_business_route_authorizations`,缺失时拒绝监听端口。`db:migrate` 和管理员初始化需要可连接的 PostgreSQL。开发机没有数据库时,可以运行 `npm run test:control-plane` 完成无数据库静态/健康烟测。 +`npm run dev` 和 `npm start` 会先执行数据库迁移,再启动控制平面;直接运行 `control-plane/src/server.ts` 或构建后的 `server.js` 时,服务也会在启动前检查必需迁移 `018_agentbus_account_workers`,缺失时拒绝监听端口。`db:migrate` 和管理员初始化需要可连接的 PostgreSQL。开发机没有数据库时,可以运行 `npm run test:control-plane` 完成无数据库静态/健康烟测。 `/health/ready` 同时检查 PostgreSQL 可用性和必需 schema 版本;迁移未完成时返回 503,并标明 `required_migration`,避免任务在数据库结构未升级时进入解析队列。 diff --git a/control-plane/migrations/018_agentbus_account_workers.sql b/control-plane/migrations/018_agentbus_account_workers.sql new file mode 100644 index 0000000..e299f02 --- /dev/null +++ b/control-plane/migrations/018_agentbus_account_workers.sql @@ -0,0 +1,100 @@ +-- Bind each employee-facing AgentBus channel and ERP browser worker to one +-- platform account. Historical AgentBus work remains deliberately unassigned: +-- ownership cannot be inferred safely from an old channel key. + +ALTER TABLE users + ADD COLUMN IF NOT EXISTS erp_account text; + +ALTER TABLE users + DROP CONSTRAINT IF EXISTS users_erp_account_length_check; + +ALTER TABLE users + ADD CONSTRAINT users_erp_account_length_check + CHECK (erp_account IS NULL OR char_length(erp_account) BETWEEN 1 AND 200); + +ALTER TABLE users + DROP CONSTRAINT IF EXISTS users_admin_erp_account_check; + +ALTER TABLE users + ADD CONSTRAINT users_admin_erp_account_check + CHECK (role <> 'admin' OR erp_account IS NULL); + +CREATE UNIQUE INDEX IF NOT EXISTS users_org_erp_account_unique_idx + ON users (organization_id, lower(erp_account)) + WHERE erp_account IS NOT NULL; + +ALTER TABLE user_channels + ADD COLUMN IF NOT EXISTS owner_user_id uuid; + +ALTER TABLE user_channels + DROP CONSTRAINT IF EXISTS user_channels_owner_scope_fkey; + +ALTER TABLE user_channels + ADD CONSTRAINT user_channels_owner_scope_fkey + FOREIGN KEY (organization_id, owner_user_id) + REFERENCES users (organization_id, id); + +CREATE UNIQUE INDEX IF NOT EXISTS user_channels_owner_unique_idx + ON user_channels (organization_id, owner_user_id) + WHERE owner_user_id IS NOT NULL; + +CREATE INDEX IF NOT EXISTS user_channels_owner_status_idx + ON user_channels (organization_id, owner_user_id, enabled, status); + +UPDATE user_channels + SET enabled = false, + status = 'error', + last_error = '渠道尚未绑定员工平台账号,监听器不会启动。', + updated_at = now() + WHERE owner_user_id IS NULL + AND enabled = true; + +ALTER TABLE tasks + ADD COLUMN IF NOT EXISTS assigned_user_id uuid; + +ALTER TABLE tasks + DROP CONSTRAINT IF EXISTS tasks_assignee_scope_fkey; + +ALTER TABLE tasks + ADD CONSTRAINT tasks_assignee_scope_fkey + FOREIGN KEY (organization_id, assigned_user_id) + REFERENCES users (organization_id, id); + +UPDATE tasks + SET assigned_user_id = created_by + WHERE assigned_user_id IS NULL + AND source = 'manual' + AND created_by IS NOT NULL; + +CREATE INDEX IF NOT EXISTS tasks_assignee_visible_created_idx + ON tasks (organization_id, assigned_user_id, created_at DESC, id DESC); + +CREATE INDEX IF NOT EXISTS tasks_assignee_queue_idx + ON tasks (organization_id, assigned_user_id, status, created_at, id) + WHERE archived_at IS NULL; + +ALTER TABLE browser_connections + ADD COLUMN IF NOT EXISTS erp_account_verified boolean NOT NULL DEFAULT false, + ADD COLUMN IF NOT EXISTS erp_account_fingerprint text; + +WITH ranked_connections AS ( + SELECT id, + row_number() OVER ( + PARTITION BY organization_id, user_id + ORDER BY last_seen_at DESC, id DESC + ) AS worker_rank + FROM browser_connections + WHERE status = 'connected' +) +UPDATE browser_connections connection + SET status = 'superseded' + FROM ranked_connections ranked + WHERE connection.id = ranked.id + AND ranked.worker_rank > 1; + +CREATE UNIQUE INDEX IF NOT EXISTS browser_connections_active_user_unique_idx + ON browser_connections (organization_id, user_id) + WHERE status = 'connected'; + +CREATE INDEX IF NOT EXISTS browser_connections_worker_freshness_idx + ON browser_connections (organization_id, user_id, status, last_seen_at DESC); diff --git a/control-plane/src/agentbus-channels.ts b/control-plane/src/agentbus-channels.ts index d3b2919..36701f8 100644 --- a/control-plane/src/agentbus-channels.ts +++ b/control-plane/src/agentbus-channels.ts @@ -1,4 +1,4 @@ -import { decryptText, encryptText } from './crypto.js'; +import { decryptText, encryptText, sha256Text } from './crypto.js'; import { getPool, withTransaction } from './db.js'; import type { AppConfig } from './config.js'; import { @@ -17,6 +17,11 @@ export interface PublicAgentBusChannel { display_name: string; external_user_ref: string | null; agentbus_bot_address: string | null; + owner_user_id: string | null; + owner_username: string | null; + owner_role: 'team_lead' | 'user' | null; + owner_erp_account: string | null; + routing_ready: boolean; enabled: boolean; status: 'disabled' | 'connecting' | 'connected' | 'error'; key_configured: true; @@ -35,6 +40,7 @@ export interface AgentBusChannelSecret extends PublicAgentBusChannel { export interface AgentBusChannelMutation { displayName: string; + ownerUserId: string; externalUserRef?: string; agentbusKey: string; botAddress?: string; @@ -43,6 +49,7 @@ export interface AgentBusChannelMutation { export interface AgentBusChannelUpdate { displayName?: string; + ownerUserId?: string; externalUserRef?: string; botAddress?: string; enabled?: boolean; @@ -66,6 +73,11 @@ function publicChannel( const status = text(row.status); const environmentManaged = text(row.external_user_ref) === LEGACY_CHANNEL_REF && legacyEnvironmentManaged; + const ownerRole = text(row.owner_role); + const routingReady = Boolean(text(row.owner_user_id)) + && booleanValue(row.owner_is_active) + && (ownerRole === 'team_lead' || ownerRole === 'user') + && Boolean(text(row.owner_erp_account)); return { id: text(row.id), organization_id: text(row.organization_id), @@ -73,14 +85,22 @@ function publicChannel( display_name: text(row.display_name), external_user_ref: text(row.external_user_ref) || null, agentbus_bot_address: text(row.agentbus_bot_address) || null, + owner_user_id: text(row.owner_user_id) || null, + owner_username: text(row.owner_username) || null, + owner_role: ownerRole === 'team_lead' || ownerRole === 'user' ? ownerRole : null, + owner_erp_account: text(row.owner_erp_account) || null, + routing_ready: routingReady, enabled: row.enabled === true || text(row.enabled) === 'true', - status: ['disabled', 'connecting', 'connected', 'error'].includes(status) + status: !routingReady && booleanValue(row.enabled) + ? 'error' + : ['disabled', 'connecting', 'connected', 'error'].includes(status) ? status as PublicAgentBusChannel['status'] : 'error', key_configured: true, deletable: !environmentManaged, last_connected_at: iso(row.last_connected_at), - last_error: text(row.last_error) || null, + last_error: text(row.last_error) + || (!routingReady && booleanValue(row.enabled) ? '渠道未绑定有效员工账号及 ERP 账号,监听器不会启动。' : null), created_at: new Date(String(row.created_at)).toISOString(), updated_at: new Date(String(row.updated_at)).toISOString() }; @@ -145,6 +165,69 @@ export class AgentBusChannelService { && Boolean(text(this.config.AGENTBUS_BOT_ADDRESS)); } + private async requireAssignableOwner( + client: import('pg').PoolClient, + organizationId: string, + ownerUserId: string + ): Promise> { + const owner = await client.query( + `SELECT id, username, role, is_active, erp_account + FROM users + WHERE organization_id = $1 AND id = $2 + FOR SHARE`, + [organizationId, ownerUserId] + ); + if (!owner.rowCount) throw new TaskError('channel_owner_not_found', '要绑定的平台账号不存在。', 404); + const row = owner.rows[0] as Record; + if (!booleanValue(row.is_active)) { + throw new TaskError('channel_owner_inactive', '停用的平台账号不能绑定 AgentBus 渠道。', 409); + } + if (!['team_lead', 'user'].includes(text(row.role))) { + throw new TaskError('channel_owner_admin_forbidden', '管理员账号不能绑定员工 AgentBus 渠道。', 409); + } + if (!text(row.erp_account)) { + throw new TaskError('channel_owner_erp_account_required', '请先为员工平台账号配置 ERP 账号。', 409); + } + return row; + } + + private async requireUniqueAgentBusKey( + client: import('pg').PoolClient, + organizationId: string, + agentbusKey: string, + exceptChannelId = '' + ): Promise { + const candidateFingerprint = sha256Text(agentbusKey); + await client.query( + `SELECT pg_advisory_xact_lock(hashtextextended($1::text || ':agentbus-key:' || $2::text, 0))`, + [organizationId, candidateFingerprint] + ); + const existing = await client.query( + `SELECT id, agentbus_ws_token_ciphertext + FROM user_channels + WHERE organization_id = $1 + AND ($2::uuid IS NULL OR id <> $2::uuid) + FOR SHARE`, + [organizationId, exceptChannelId || null] + ); + for (const row of existing.rows as Record[]) { + try { + const existingKey = decryptText(this.config, text(row.agentbus_ws_token_ciphertext)); + if (existingKey && sha256Text(existingKey) === candidateFingerprint) { + throw new TaskError( + 'channel_key_conflict', + '该 AgentBus key 已用于另一个用户渠道;每个渠道必须使用独立 key。', + 409 + ); + } + } catch (error) { + if (error instanceof TaskError) throw error; + // A separately corrupted historical key is handled by listener + // loading and must not disclose or block comparison of valid keys. + } + } + } + async list(organizationId: string): Promise { const result = await getPool(this.config).query( `WITH canonical_legacy AS ( @@ -155,9 +238,12 @@ export class AgentBusChannelService { ) SELECT channel.id, channel.organization_id, channel.display_name, channel.external_user_ref, channel.agentbus_bot_address, channel.enabled, channel.status, channel.last_connected_at, - channel.last_error, channel.created_at, channel.updated_at + channel.last_error, channel.created_at, channel.updated_at, + channel.owner_user_id, owner.username AS owner_username, owner.role AS owner_role, + owner.is_active AS owner_is_active, owner.erp_account AS owner_erp_account FROM user_channels channel LEFT JOIN canonical_legacy legacy ON legacy.id = channel.id + LEFT JOIN users owner ON owner.id = channel.owner_user_id WHERE channel.organization_id = $1 AND (channel.external_user_ref IS DISTINCT FROM '${LEGACY_CHANNEL_REF}' OR legacy.id IS NOT NULL) ORDER BY channel.created_at ASC, channel.id ASC`, @@ -178,9 +264,16 @@ export class AgentBusChannelService { ) SELECT channel.id, channel.organization_id, channel.display_name, channel.external_user_ref, channel.agentbus_bot_address, channel.enabled, channel.status, channel.last_connected_at, - channel.last_error, channel.created_at, channel.updated_at, channel.agentbus_ws_token_ciphertext + channel.last_error, channel.created_at, channel.updated_at, channel.agentbus_ws_token_ciphertext, + channel.owner_user_id, owner.username AS owner_username, owner.role AS owner_role, + owner.is_active AS owner_is_active, owner.erp_account AS owner_erp_account FROM user_channels channel LEFT JOIN canonical_legacy legacy ON legacy.id = channel.id + JOIN users owner ON owner.id = channel.owner_user_id + AND owner.organization_id = channel.organization_id + AND owner.is_active = true + AND owner.role IN ('team_lead', 'user') + AND owner.erp_account IS NOT NULL WHERE channel.organization_id = $1 AND channel.enabled = true AND (channel.external_user_ref IS DISTINCT FROM '${LEGACY_CHANNEL_REF}' OR legacy.id IS NOT NULL) @@ -234,18 +327,19 @@ export class AgentBusChannelService { if (!agentbusKey) throw new TaskError('channel_key_required', 'AgentBus key 不能为空。', 400); try { const result = await withTransaction(this.config, async (client) => { + const owner = await this.requireAssignableOwner(client, context.organizationId, text(input.ownerUserId)); + await this.requireUniqueAgentBusKey(client, context.organizationId, agentbusKey); const inserted = await client.query( `INSERT INTO user_channels - (organization_id, display_name, external_user_ref, + (organization_id, display_name, owner_user_id, external_user_ref, agentbus_ws_token_ciphertext, agentbus_bot_address, enabled, status, created_by) - VALUES ($1, $2, $3, $4, $5, $6, $7, $8) - RETURNING id, organization_id, display_name, external_user_ref, - agentbus_bot_address, enabled, status, last_connected_at, - last_error, created_at, updated_at`, + VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) + RETURNING id`, [ context.organizationId, displayName, + text(owner.id), text(input.externalUserRef).slice(0, 200) || null, encryptText(this.config, agentbusKey), text(input.botAddress).slice(0, 200) || null, @@ -256,14 +350,18 @@ export class AgentBusChannelService { ); await this.audit(client, context, 'agentbus_channel.created', text(inserted.rows[0].id), { display_name: displayName, + owner_user_id: text(owner.id), external_user_ref_present: Boolean(text(input.externalUserRef)), enabled: input.enabled !== false }); - return inserted.rows[0] as Record; + return text(inserted.rows[0].id); }); - return publicChannel(result, this.legacyEnvironmentManaged()); + return publicChannel(await this.getRow(context.organizationId, result), this.legacyEnvironmentManaged()); } catch (error) { if (error && typeof error === 'object' && String((error as { code?: unknown }).code || '') === '23505') { + if (String((error as { constraint?: unknown }).constraint || '').includes('owner')) { + throw new TaskError('channel_owner_conflict', '该平台账号已经绑定另一个 AgentBus 渠道。', 409); + } throw new TaskError('channel_name_conflict', '同一组织下的渠道名称已存在。', 409); } throw error; @@ -289,21 +387,42 @@ export class AgentBusChannelService { : input.enabled; try { const result = await withTransaction(this.config, async (client) => { + const ownerUserId = input.ownerUserId === undefined + ? text(current.owner_user_id) + : text(input.ownerUserId); + const owner = await this.requireAssignableOwner(client, context.organizationId, ownerUserId); + if (enabled) { + const keyResult = await client.query( + `SELECT agentbus_ws_token_ciphertext + FROM user_channels + WHERE organization_id = $1 AND id = $2 + FOR SHARE`, + [context.organizationId, channelId] + ); + try { + const currentKey = decryptText(this.config, text(keyResult.rows[0]?.agentbus_ws_token_ciphertext)); + if (!currentKey) throw new Error('empty AgentBus key'); + await this.requireUniqueAgentBusKey(client, context.organizationId, currentKey, channelId); + } catch (error) { + if (error instanceof TaskError) throw error; + throw new TaskError('channel_key_invalid', '渠道 key 无法解密;请先轮换 key。', 409); + } + } const updated = await client.query( `UPDATE user_channels SET display_name = $1, - external_user_ref = $2, - agentbus_bot_address = $3, - enabled = $4, - status = CASE WHEN $4 THEN 'connecting' ELSE 'disabled' END, - last_error = CASE WHEN $4 THEN NULL ELSE last_error END, + owner_user_id = $2, + external_user_ref = $3, + agentbus_bot_address = $4, + enabled = $5, + status = CASE WHEN $5 THEN 'connecting' ELSE 'disabled' END, + last_error = CASE WHEN $5 THEN NULL ELSE last_error END, updated_at = now() - WHERE organization_id = $5 AND id = $6 - RETURNING id, organization_id, display_name, external_user_ref, - agentbus_bot_address, enabled, status, last_connected_at, - last_error, created_at, updated_at`, + WHERE organization_id = $6 AND id = $7 + RETURNING id`, [ displayName, + text(owner.id), externalUserRef, input.botAddress === undefined ? (text(current.agentbus_bot_address) || null) @@ -316,13 +435,17 @@ export class AgentBusChannelService { if (!updated.rowCount) throw new TaskError('channel_not_found', '用户渠道不存在。', 404); await this.audit(client, context, 'agentbus_channel.updated', channelId, { enabled, - display_name: displayName + display_name: displayName, + owner_user_id: text(owner.id) }); - return updated.rows[0] as Record; + return text(updated.rows[0].id); }); - return publicChannel(result, this.legacyEnvironmentManaged()); + return publicChannel(await this.getRow(context.organizationId, result), this.legacyEnvironmentManaged()); } catch (error) { if (error && typeof error === 'object' && String((error as { code?: unknown }).code || '') === '23505') { + if (String((error as { constraint?: unknown }).constraint || '').includes('owner')) { + throw new TaskError('channel_owner_conflict', '该平台账号已经绑定另一个 AgentBus 渠道。', 409); + } throw new TaskError('channel_name_conflict', '同一组织下的渠道名称已存在。', 409); } throw error; @@ -333,6 +456,7 @@ export class AgentBusChannelService { const key = text(agentbusKey); if (!key) throw new TaskError('channel_key_required', 'AgentBus key 不能为空。', 400); const result = await withTransaction(this.config, async (client) => { + await this.requireUniqueAgentBusKey(client, context.organizationId, key, channelId); const updated = await client.query( `UPDATE user_channels SET agentbus_ws_token_ciphertext = $1, @@ -340,16 +464,14 @@ export class AgentBusChannelService { last_error = NULL, updated_at = now() WHERE organization_id = $2 AND id = $3 - RETURNING id, organization_id, display_name, external_user_ref, - agentbus_bot_address, enabled, status, last_connected_at, - last_error, created_at, updated_at`, + RETURNING id`, [encryptText(this.config, key), context.organizationId, channelId] ); if (!updated.rowCount) throw new TaskError('channel_not_found', '用户渠道不存在。', 404); await this.audit(client, context, 'agentbus_channel.key_rotated', channelId, {}); - return updated.rows[0] as Record; + return text(updated.rows[0].id); }); - return publicChannel(result, this.legacyEnvironmentManaged()); + return publicChannel(await this.getRow(context.organizationId, result), this.legacyEnvironmentManaged()); } async delete( @@ -426,8 +548,9 @@ export class AgentBusChannelService { await client.query( `INSERT INTO user_channels (organization_id, display_name, external_user_ref, - agentbus_ws_token_ciphertext, agentbus_bot_address, enabled, status) - SELECT $1, '默认 AgentBus 渠道', '${LEGACY_CHANNEL_REF}', $2, $3, true, 'connecting' + agentbus_ws_token_ciphertext, agentbus_bot_address, enabled, status, last_error) + SELECT $1, '默认 AgentBus 渠道', '${LEGACY_CHANNEL_REF}', $2, $3, false, 'error', + '兼容渠道尚未绑定员工平台账号,监听器不会启动。' WHERE NOT EXISTS ( SELECT 1 FROM user_channels WHERE organization_id = $1 AND external_user_ref = '${LEGACY_CHANNEL_REF}' @@ -475,11 +598,14 @@ export class AgentBusChannelService { private async getRow(organizationId: string, channelId: string): Promise> { const result = await getPool(this.config).query( - `SELECT id, organization_id, display_name, external_user_ref, - agentbus_bot_address, enabled, status, last_connected_at, - last_error, created_at, updated_at - FROM user_channels - WHERE organization_id = $1 AND id = $2`, + `SELECT channel.id, channel.organization_id, channel.display_name, channel.external_user_ref, + channel.agentbus_bot_address, channel.enabled, channel.status, channel.last_connected_at, + channel.last_error, channel.created_at, channel.updated_at, + channel.owner_user_id, owner.username AS owner_username, owner.role AS owner_role, + owner.is_active AS owner_is_active, owner.erp_account AS owner_erp_account + FROM user_channels channel + LEFT JOIN users owner ON owner.id = channel.owner_user_id + WHERE channel.organization_id = $1 AND channel.id = $2`, [organizationId, channelId] ); if (!result.rowCount) throw new TaskError('channel_not_found', '用户渠道不存在。', 404); @@ -590,7 +716,9 @@ export class AgentBusManager { displayName: channel.display_name, wsUrl: channel.ws_url, wsToken: channel.ws_token, - botAddress: channel.bot_address + botAddress: channel.bot_address, + ownerUserId: channel.owner_user_id as string, + ownerRole: channel.owner_role as 'team_lead' | 'user' }, onStatusChange: (status, error, epoch) => this.channels.setRuntimeStatus( this.options.organizationId, diff --git a/control-plane/src/agentbus.ts b/control-plane/src/agentbus.ts index 65d35a7..422a18c 100644 --- a/control-plane/src/agentbus.ts +++ b/control-plane/src/agentbus.ts @@ -11,7 +11,8 @@ import type { TaskEvent, TaskMessageInput, TaskMessageResult, - AgentBusAttachmentContent + AgentBusAttachmentContent, + TaskRole } from './task-service.js'; import { InputAttachmentError, @@ -138,6 +139,8 @@ export interface AgentBusChannelConnection { wsUrl: string; wsToken: string; botAddress: string; + ownerUserId: string; + ownerRole: Exclude; } export interface AgentBusListenerOptions { @@ -1009,8 +1012,9 @@ export class AgentBusListener { }; const context: TaskContext = { organizationId: this.organizationId, - userId: '', + userId: this.channel?.ownerUserId || '', requestId: `agentbus:${taskId}`, + role: this.channel?.ownerRole, source: 'agentbus', ...(this.channel ? { channelId: this.channel.id } : {}) }; diff --git a/control-plane/src/auth.ts b/control-plane/src/auth.ts index 2636848..7123182 100644 --- a/control-plane/src/auth.ts +++ b/control-plane/src/auth.ts @@ -20,12 +20,14 @@ export interface AuthUser { organizationId: string; username: string; role: AuthRole; + erpAccount: string | null; } export interface PublicAccount { id: string; username: string; role: AuthRole; + erp_account: string | null; is_active: boolean; authorized_business_route_ids: BusinessRouteId[]; business_authorization_revision: number; @@ -78,6 +80,27 @@ function validatePassword(value: string): string { return password; } +function normalizeErpAccount(value: unknown): string | null { + const normalized = String(value ?? '').trim(); + if (!normalized) return null; + if (normalized.length > 200) { + throw new AuthError('erp_account_invalid', 'ERP 账号必须为 1—200 个字符。', 400); + } + return normalized; +} + +function validateAccountRouting(role: AuthRole, value: unknown): string | null { + const erpAccount = normalizeErpAccount(value); + if (role === 'admin') { + if (erpAccount) throw new AuthError('admin_erp_account_forbidden', '管理员账号不能绑定员工 ERP 账号。', 409); + return null; + } + if (!erpAccount) { + throw new AuthError('erp_account_required', '普通用户或组长必须绑定 ERP 账号。', 400); + } + return erpAccount; +} + function normalizeRole(value: unknown): AuthRole { if (value === 'admin' || value === 'team_lead') return value; return 'user'; @@ -109,7 +132,8 @@ function mapUser(row: Record): AuthUser { id: String(row.id), organizationId: String(row.organization_id), username: String(row.username), - role: normalizeRole(row.role) + role: normalizeRole(row.role), + erpAccount: normalizeErpAccount(row.erp_account) }; } @@ -122,6 +146,7 @@ function mapAccount(row: Record): PublicAccount { id: String(row.id), username: String(row.username), role, + erp_account: normalizeErpAccount(row.erp_account), is_active: row.is_active === true || String(row.is_active) === 'true', authorized_business_route_ids: role === 'admin' ? [...ALL_BUSINESS_ROUTE_IDS] : storedRouteIds, business_authorization_revision: Math.max(0, Number(row.business_authorization_revision || 0)), @@ -137,7 +162,7 @@ async function loadPublicAccount( userId: string ): Promise { const result = await client.query( - `SELECT u.id, u.username, u.role, u.is_active, + `SELECT u.id, u.username, u.role, u.erp_account, u.is_active, u.business_authorization_revision, u.last_login_at, u.created_at, u.updated_at, COALESCE(ARRAY( @@ -209,21 +234,34 @@ export class AuthService { const result = existing.rowCount ? await client.query( `UPDATE users - SET password_hash = $1, role = 'admin', is_active = true, + SET password_hash = $1, role = 'admin', erp_account = NULL, is_active = true, must_change_password = false, password_changed_at = now(), failed_login_count = 0, locked_until = NULL, updated_at = now() WHERE id = $2 - RETURNING id, organization_id, username, role`, + RETURNING id, organization_id, username, role, erp_account`, [passwordHash, existing.rows[0].id] ) : await client.query( `INSERT INTO users (organization_id, username, password_hash, role) VALUES ($1, $2, $3, 'admin') - RETURNING id, organization_id, username, role`, + RETURNING id, organization_id, username, role, erp_account`, [organization.id, normalized, passwordHash] ); const user = mapUser(result.rows[0]); if (existing.rowCount && force) { + await client.query( + `UPDATE user_channels + SET owner_user_id = NULL, enabled = false, status = 'disabled', + last_error = '绑定账号已重置为管理员,渠道已解除绑定。', updated_at = now() + WHERE organization_id = $1 AND owner_user_id = $2`, + [organization.id, user.id] + ); + await client.query( + `UPDATE browser_connections + SET status = 'superseded', erp_account_verified = false + WHERE organization_id = $1 AND user_id = $2 AND status = 'connected'`, + [organization.id, user.id] + ); await client.query( 'UPDATE sessions SET revoked_at = now() WHERE user_id = $1 AND revoked_at IS NULL', [user.id] @@ -237,7 +275,7 @@ export class AuthService { const normalized = normalizeUsername(username); const pool = getPool(this.config); const lookup = await pool.query( - `SELECT id, organization_id, username, password_hash, role, is_active, + `SELECT id, organization_id, username, password_hash, role, erp_account, is_active, failed_login_count, locked_until FROM users WHERE organization_id = (SELECT id FROM organizations WHERE slug = $1) @@ -330,7 +368,7 @@ export class AuthService { async listAccounts(actor: AuthUser): Promise { this.requireAdmin(actor); const result = await getPool(this.config).query( - `SELECT u.id, u.username, u.role, u.is_active, + `SELECT u.id, u.username, u.role, u.erp_account, u.is_active, u.business_authorization_revision, u.last_login_at, u.created_at, u.updated_at, COALESCE(ARRAY( @@ -353,6 +391,7 @@ export class AuthService { username: string; password: string; role: AuthRole; + erpAccount?: string; businessRouteIds?: readonly string[]; }, requestId: string @@ -361,8 +400,10 @@ export class AuthService { const username = validateUsername(input.username); const passwordHash = await argon2.hash(validatePassword(input.password), { type: argon2.argon2id }); const role = normalizeRole(input.role); + const erpAccount = validateAccountRouting(role, input.erpAccount); const businessRouteIds = role === 'admin' ? [] : normalizeBusinessRouteIds(input.businessRouteIds); - return withTransaction(this.config, async (client) => { + try { + return await withTransaction(this.config, async (client) => { await client.query( `SELECT pg_advisory_xact_lock(hashtextextended($1::text || ':' || $2::text, 0))`, [actor.organizationId, username] @@ -374,10 +415,10 @@ export class AuthService { if (existing.rowCount) throw new AuthError('account_exists', '该账号已存在。', 409); const created = await client.query( `INSERT INTO users - (organization_id, username, password_hash, role, password_changed_at) - VALUES ($1, $2, $3, $4, now()) + (organization_id, username, password_hash, role, erp_account, password_changed_at) + VALUES ($1, $2, $3, $4, $5, now()) RETURNING id`, - [actor.organizationId, username, passwordHash, role] + [actor.organizationId, username, passwordHash, role, erpAccount] ); const accountId = String(created.rows[0].id); if (businessRouteIds.length) { @@ -393,25 +434,36 @@ export class AuthService { if (!account) throw new AuthError('account_not_found', '账号创建后未能读取。', 500); await this.accountAudit(client, actor, 'account.created', account.id, requestId, { role, + erp_account_configured: Boolean(erpAccount), authorized_business_route_ids: account.authorized_business_route_ids }); - return account; - }); + return account; + }); + } catch (error) { + if (error && typeof error === 'object' && String((error as { code?: unknown }).code || '') === '23505') { + const constraint = String((error as { constraint?: unknown }).constraint || ''); + if (constraint.includes('erp_account')) { + throw new AuthError('erp_account_conflict', '该 ERP 账号已经绑定另一个平台账号。', 409); + } + } + throw error; + } } async updateAccount( actor: AuthUser, targetUserId: string, - input: { role?: AuthRole; isActive?: boolean }, + input: { role?: AuthRole; isActive?: boolean; erpAccount?: string | null }, requestId: string ): Promise { this.requireAdmin(actor); - if (input.role === undefined && input.isActive === undefined) { + if (input.role === undefined && input.isActive === undefined && input.erpAccount === undefined) { throw new AuthError('account_update_empty', '没有需要更新的账号字段。', 400); } - return withTransaction(this.config, async (client) => { + try { + return await withTransaction(this.config, async (client) => { const target = await client.query( - `SELECT id, username, role, is_active, + `SELECT id, username, role, erp_account, is_active, last_login_at, created_at, updated_at FROM users WHERE organization_id = $1 AND id = $2 @@ -422,6 +474,12 @@ export class AuthService { const before = mapAccount(target.rows[0] as Record); const role = input.role === undefined ? before.role : normalizeRole(input.role); const isActive = input.isActive === undefined ? before.is_active : input.isActive; + const erpAccount = validateAccountRouting( + role, + role === 'admin' + ? null + : input.erpAccount === undefined ? before.erp_account : input.erpAccount + ); const removesActiveAdmin = before.role === 'admin' && before.is_active && (role !== 'admin' || !isActive); if (actor.id === before.id && (role !== 'admin' || !isActive)) { throw new AuthError('self_lockout_forbidden', '不能停用或降级当前登录的管理员账号。', 409); @@ -439,28 +497,56 @@ export class AuthService { } const updated = await client.query( `UPDATE users - SET role = $1, is_active = $2, updated_at = now() - WHERE organization_id = $3 AND id = $4 + SET role = $1, is_active = $2, erp_account = $3, updated_at = now() + WHERE organization_id = $4 AND id = $5 RETURNING id`, - [role, isActive, actor.organizationId, before.id] + [role, isActive, erpAccount, actor.organizationId, before.id] ); - if (before.role !== role || before.is_active !== isActive) { + const routingIdentityChanged = before.erp_account !== erpAccount; + if (before.role !== role || before.is_active !== isActive || routingIdentityChanged) { await client.query( 'UPDATE sessions SET revoked_at = now() WHERE user_id = $1 AND revoked_at IS NULL', [before.id] ); } + if (!isActive || role === 'admin') { + await client.query( + `UPDATE user_channels + SET owner_user_id = NULL, enabled = false, status = 'disabled', + last_error = $1, updated_at = now() + WHERE organization_id = $2 AND owner_user_id = $3`, + [role === 'admin' ? '绑定账号已变更为管理员,渠道已解除绑定。' : '绑定账号已停用,渠道已解除绑定。', actor.organizationId, before.id] + ); + } + if (!isActive || routingIdentityChanged || before.role !== role) { + await client.query( + `UPDATE browser_connections + SET status = 'superseded', erp_account_verified = false + WHERE organization_id = $1 AND user_id = $2 AND status = 'connected'`, + [actor.organizationId, before.id] + ); + } await this.accountAudit(client, actor, 'account.updated', before.id, requestId, { previous_role: before.role, role, previous_active: before.is_active, active: isActive, - sessions_revoked: before.role !== role || before.is_active !== isActive + erp_account_changed: routingIdentityChanged, + sessions_revoked: before.role !== role || before.is_active !== isActive || routingIdentityChanged }); const account = await loadPublicAccount(client, actor.organizationId, String(updated.rows[0].id)); if (!account) throw new AuthError('account_not_found', '账号更新后未能读取。', 500); - return account; - }); + return account; + }); + } catch (error) { + if (error && typeof error === 'object' && String((error as { code?: unknown }).code || '') === '23505') { + const constraint = String((error as { constraint?: unknown }).constraint || ''); + if (constraint.includes('erp_account')) { + throw new AuthError('erp_account_conflict', '该 ERP 账号已经绑定另一个平台账号。', 409); + } + } + throw error; + } } async setBusinessRouteAuthorizations( @@ -648,7 +734,7 @@ export class AuthService { async getActiveSession(token: string | undefined): Promise { if (!token) return null; const result = await getPool(this.config).query( - `SELECT s.id AS session_id, s.csrf_token_hash, u.id, u.organization_id, u.username, u.role + `SELECT s.id AS session_id, s.csrf_token_hash, u.id, u.organization_id, u.username, u.role, u.erp_account FROM sessions s JOIN users u ON u.id = s.user_id WHERE s.token_hash = $1 @@ -671,7 +757,8 @@ export class AuthService { id: row.id, organization_id: row.organization_id, username: row.username, - role: row.role + role: row.role, + erp_account: row.erp_account }) }; } diff --git a/control-plane/src/db.ts b/control-plane/src/db.ts index f5d952e..fe94de2 100644 --- a/control-plane/src/db.ts +++ b/control-plane/src/db.ts @@ -5,7 +5,7 @@ import { writeEmergencyDiagnostic } from './diagnostics.js'; const { Pool } = pg; let pool: pg.Pool | null = null; -export const REQUIRED_SCHEMA_VERSION = '017_user_business_route_authorizations'; +export const REQUIRED_SCHEMA_VERSION = '018_agentbus_account_workers'; export interface DatabaseReadiness { ready: boolean; diff --git a/control-plane/src/server.ts b/control-plane/src/server.ts index 52197ab..41d9bfe 100644 --- a/control-plane/src/server.ts +++ b/control-plane/src/server.ts @@ -63,6 +63,7 @@ const accountCreateSchema = z.object({ username: z.string().min(1).max(160), password: z.string().min(1), role: z.enum(['admin', 'team_lead', 'user']).default('user'), + erp_account: z.string().trim().max(200).optional(), business_route_ids: z.array( z.string().trim().refine((routeId) => Boolean(businessRouteById(routeId)), '业务类型不存在。') ).max(BUSINESS_ROUTES.length).default([]) @@ -71,8 +72,9 @@ const accountCreateSchema = z.object({ const accountUpdateSchema = z.object({ role: z.enum(['admin', 'team_lead', 'user']).optional(), - is_active: z.boolean().optional() -}).refine((body) => body.role !== undefined || body.is_active !== undefined, { + is_active: z.boolean().optional(), + erp_account: z.string().trim().max(200).nullable().optional() +}).refine((body) => body.role !== undefined || body.is_active !== undefined || body.erp_account !== undefined, { message: '至少提供一个账号更新字段。' }); @@ -123,6 +125,8 @@ const resultSchema = z.object({ const heartbeatSchema = z.object({ connection_id: z.string().min(1).max(200), extension_version: z.string().max(80).optional(), + erp_account: z.string().trim().max(200).optional(), + erp_account_matched: z.boolean().optional(), metadata: z.record(z.unknown()).optional() }); const automationSettingsSchema = z.object({ enabled: z.boolean() }); @@ -138,6 +142,7 @@ const parserDecisionReviewSchema = z.object({ }); const channelCreateSchema = z.object({ display_name: z.string().min(1).max(120), + owner_user_id: z.string().uuid(), external_user_ref: z.string().max(200).optional(), agentbus_key: z.string().min(1).max(4_000), bot_address: z.string().max(200).optional(), @@ -145,6 +150,7 @@ const channelCreateSchema = z.object({ }); const channelUpdateSchema = z.object({ display_name: z.string().min(1).max(120).optional(), + owner_user_id: z.string().uuid().optional(), external_user_ref: z.string().max(200).optional(), bot_address: z.string().max(200).optional(), enabled: z.boolean().optional() @@ -156,6 +162,7 @@ const listTasksQuerySchema = z.object({ limit: z.coerce.number().int().min(1).max(200).default(200), offset: z.coerce.number().int().min(0).max(1_000_000).default(0), archive: z.enum(['active', 'archived', 'all']).default('active'), + executable_by: z.enum(['me']).optional(), include_total: z.preprocess( (value) => value === undefined ? true : String(value).toLowerCase() !== 'false', z.boolean() @@ -361,7 +368,8 @@ function publicUser(session: ActiveSession) { return { id: session.user.id, username: session.user.username, - role: session.user.role + role: session.user.role, + erp_account: session.user.erpAccount }; } @@ -963,6 +971,7 @@ export async function buildServer({ username: body.username, password: body.password, role: body.role, + erpAccount: body.erp_account, businessRouteIds: body.business_route_ids }, requestId(request)); return { ok: true, account }; @@ -975,8 +984,10 @@ export async function buildServer({ const body = accountUpdateSchema.parse(request.body); const account = await auth.updateAccount(session.user, userId, { role: body.role, - isActive: body.is_active + isActive: body.is_active, + erpAccount: body.erp_account }, requestId(request)); + await agentBus?.reload(); return { ok: true, account }; }); @@ -1098,6 +1109,7 @@ export async function buildServer({ const body = channelCreateSchema.parse(request.body); const channel = await channelService.create(contextFor(session, request), { displayName: body.display_name, + ownerUserId: body.owner_user_id, externalUserRef: body.external_user_ref, agentbusKey: body.agentbus_key, botAddress: body.bot_address, @@ -1113,6 +1125,7 @@ export async function buildServer({ const body = channelUpdateSchema.parse(request.body); const channel = await channelService.update(contextFor(session, request), params.channelId, { displayName: body.display_name, + ownerUserId: body.owner_user_id, externalUserRef: body.external_user_ref, botAddress: body.bot_address, enabled: body.enabled @@ -1162,6 +1175,7 @@ export async function buildServer({ offset: query.offset, includeTotal: query.include_total, archive: query.archive, + assignedToUserId: query.executable_by === 'me' ? session.user.id : undefined, access: contextFor(session, request) }); return { @@ -1334,8 +1348,17 @@ export async function buildServer({ app.post('/api/connections/heartbeat', async (request) => { const session = await requireMutationSession(request); const body = heartbeatSchema.parse(request.body); - await tasks.heartbeat(contextFor(session, request), body.connection_id, body.extension_version || '', body.metadata || {}); - return { ok: true, connected: true }; + const worker = await tasks.heartbeat( + contextFor(session, request), + body.connection_id, + body.extension_version || '', + body.metadata || {}, + { + erpAccount: body.erp_account || '', + erpAccountMatched: body.erp_account_matched === true + } + ); + return { ok: true, connected: true, ...worker }; }); app.get('/api/audit', async (request) => { @@ -1403,8 +1426,7 @@ export async function buildServer({ const send = (event: TaskEvent) => { if (event.organization_id !== session.user.organizationId) return; if (!canAccessTask(contextFor(session, request), { - createdBy: event.owner_user_id, - source: event.task_source || 'agentbus' + assignedUserId: event.owner_user_id })) return; const publicEvent = { id: event.id, diff --git a/control-plane/src/task-service.ts b/control-plane/src/task-service.ts index 17cb4a1..e4715c5 100644 --- a/control-plane/src/task-service.ts +++ b/control-plane/src/task-service.ts @@ -345,6 +345,7 @@ export interface PublicTask { confirmation_mode: ConfirmationMode; confirmed_at: string | null; creator?: PublicTaskActor | null; + assignee?: PublicTaskActor | null; archived_at?: string | null; archived_by?: PublicTaskActor | null; archive_reason?: string | null; @@ -382,6 +383,7 @@ export interface PublicTaskSummary { confirmation_mode: ConfirmationMode; confirmed_at: string | null; creator?: PublicTaskActor | null; + assignee?: PublicTaskActor | null; archived_at?: string | null; archived_by?: PublicTaskActor | null; archive_reason?: string | null; @@ -547,12 +549,11 @@ export function canViewOperationsDashboard(role: TaskRole | undefined): boolean export function canAccessTask( access: TaskAccessScope, - task: { createdBy: string | null | undefined; source: TaskSource } + task: { assignedUserId: string | null | undefined } ): boolean { if (!isTaskOwnerRestricted(access.role)) return true; return Boolean(access.userId) - && task.createdBy === access.userId - && task.source === 'manual'; + && task.assignedUserId === access.userId; } export function canExecuteBusinessRoute({ @@ -566,8 +567,7 @@ export function canExecuteBusinessRoute({ routeId: BusinessRouteId | null; authorizedRouteIds: readonly BusinessRouteId[]; }): boolean { - if (source !== 'manual') return true; - if (role === 'admin') return true; + if (role === 'admin') return source === 'manual'; if (!isTaskOwnerRestricted(role) || !routeId) return false; return authorizedRouteIds.includes(routeId); } @@ -2506,16 +2506,6 @@ export class TaskService { ): Promise { const source = normalizeTaskSource(context.source); const route = businessRouteById(routeId); - if (source !== 'manual') { - return { - allowed: true, - code: '', - message: '', - routeId: route?.routeId || null, - directive: route?.directive || null, - authorizationRevision: 0 - }; - } const user = await client.query( `SELECT u.role, u.is_active, u.business_authorization_revision, EXISTS ( @@ -2622,7 +2612,7 @@ export class TaskService { private taskAuthorizationContext(row: Record, requestId: string): TaskContext { return { organizationId: text(row.organization_id), - userId: text(row.created_by), + userId: text(row.assigned_user_id), requestId, source: normalizeTaskSource(row.source) }; @@ -2641,8 +2631,8 @@ export class TaskService { if (!decision.allowed) { const base = this.businessAuthorizationError(decision); const message = decision.directive - ? `该任务的创建账号未授权“${decision.directive}”业务,已禁止执行。请先由管理员重新授权。` - : '该任务的创建账号无法获得明确任务类型授权,已禁止执行。'; + ? `该任务的归属账号未授权“${decision.directive}”业务,已禁止执行。请先由管理员重新授权。` + : '该任务的归属账号无法获得明确任务类型授权,已禁止执行。'; throw new TaskError(base.code, message, base.statusCode, base.details); } return decision; @@ -2677,7 +2667,7 @@ export class TaskService { `SELECT * FROM tasks WHERE organization_id = $1 AND task_id = $2 - AND ($3::boolean = false OR (created_by = $4 AND source = 'manual')) + AND ($3::boolean = false OR assigned_user_id = $4) FOR UPDATE`, [context.organizationId, taskId, this.isOwnerRestrictedUser(context), context.userId || null] ); @@ -2695,14 +2685,35 @@ export class TaskService { connectionId: string ): Promise { const connection = await client.query( - `SELECT id FROM browser_connections - WHERE organization_id = $1 AND user_id = $2 AND connection_id = $3 - FOR UPDATE`, + `SELECT connection.id, connection.status, connection.last_seen_at, + connection.erp_account_verified, connection.erp_account_fingerprint, + account.role, account.erp_account + FROM browser_connections connection + JOIN users account ON account.id = connection.user_id + WHERE connection.organization_id = $1 + AND connection.user_id = $2 + AND connection.connection_id = $3 + FOR UPDATE OF connection, account`, [context.organizationId, context.userId, connectionId] ); if (!connection.rowCount) { throw new TaskError('browser_connection_not_owned', '浏览器连接不存在或不属于当前账号,请重新连接。', 403); } + const row = connection.rows[0] as Record; + const lastSeenAt = Date.parse(text(row.last_seen_at)); + if (text(row.status) !== 'connected' || !Number.isFinite(lastSeenAt) || Date.now() - lastSeenAt > 90_000) { + throw new TaskError('browser_worker_stale', '当前云电脑执行连接已过期,请等待插件重新连接。', 409); + } + const expectedErpAccount = text(row.erp_account); + if (text(row.role) !== 'admin') { + if (!expectedErpAccount) { + throw new TaskError('erp_account_not_configured', '当前平台账号尚未配置 ERP 账号,禁止执行。', 409); + } + const expectedFingerprint = sha256Text(expectedErpAccount.toLocaleLowerCase()); + if (!databaseBoolean(row.erp_account_verified) || text(row.erp_account_fingerprint) !== expectedFingerprint) { + throw new TaskError('erp_account_mismatch', '当前云电脑登录的 ERP 账号与平台绑定账号不一致,禁止执行。', 409); + } + } } private log( @@ -2770,6 +2781,7 @@ export class TaskService { confirmation_mode: text(row.confirmation_mode) === 'automatic' ? 'automatic' : 'manual', confirmed_at: row.confirmed_at ? new Date(String(row.confirmed_at)).toISOString() : null, creator: publicActor(row.creator_id, row.creator_username), + assignee: publicActor(row.assignee_id, row.assignee_username), archived_at: row.archived_at ? new Date(String(row.archived_at)).toISOString() : null, archived_by: publicActor(row.archived_by_id, row.archived_by_username), archive_reason: text(row.archive_reason) || null, @@ -2826,6 +2838,7 @@ export class TaskService { confirmation_mode: text(row.confirmation_mode) === 'automatic' ? 'automatic' : 'manual', confirmed_at: row.confirmed_at ? new Date(String(row.confirmed_at)).toISOString() : null, creator: publicActor(row.creator_id, row.creator_username), + assignee: publicActor(row.assignee_id, row.assignee_username), archived_at: row.archived_at ? new Date(String(row.archived_at)).toISOString() : null, archived_by: publicActor(row.archived_by_id, row.archived_by_username), archive_reason: text(row.archive_reason) || null, @@ -2898,7 +2911,7 @@ export class TaskService { message: text(eventRow.message), payload: jsonObject(eventRow.payload), created_at: new Date(String(eventRow.created_at)).toISOString(), - owner_user_id: text(row.created_by) || null, + owner_user_id: text(row.assigned_user_id) || null, task_source: normalizeTaskSource(row.source) }; } @@ -3546,7 +3559,7 @@ export class TaskService { JOIN agent_sessions s ON s.task_id = t.id WHERE t.organization_id = $1 AND t.task_id = $2 AND t.archived_at IS NULL - AND ($3::boolean = false OR (t.created_by = $4 AND t.source = 'manual'))`, + AND ($3::boolean = false OR t.assigned_user_id = $4)`, [context.organizationId, taskId, this.isOwnerRestrictedUser(context), context.userId || null] ); if (!result.rowCount) throw new TaskError('task_not_found', '任务不存在。', 404); @@ -3564,7 +3577,7 @@ export class TaskService { AND t.archived_at IS NULL AND s.conversation_id = $2 AND ($3::uuid IS NULL OR t.channel_id = $3::uuid) - AND ($4::boolean = false OR (t.created_by = $5 AND t.source = 'manual')) + AND ($4::boolean = false OR t.assigned_user_id = $5) ORDER BY t.updated_at DESC`, [ context.organizationId, @@ -3690,7 +3703,7 @@ export class TaskService { JOIN agent_sessions s ON s.task_id = t.id WHERE t.organization_id = $1 AND t.id = $2 AND t.archived_at IS NULL - AND ($3::boolean = false OR (t.created_by = $4 AND t.source = 'manual')) + AND ($3::boolean = false OR t.assigned_user_id = $4) FOR UPDATE OF t, s`, [context.organizationId, target.rowId, this.isOwnerRestrictedUser(context), context.userId || null] ); @@ -3728,7 +3741,7 @@ export class TaskService { AND i.scope = 'agent_message' AND i.idempotency_key = $2 AND i.actor_user_id IS NOT DISTINCT FROM $3::uuid - AND ($4::boolean = false OR (t.created_by = $5 AND t.source = 'manual')) + AND ($4::boolean = false OR t.assigned_user_id = $5) FOR UPDATE`, [ context.organizationId, @@ -4027,7 +4040,7 @@ export class TaskService { JOIN tasks t ON t.id = i.task_id WHERE i.organization_id = $1 AND i.scope = 'create_task' AND i.idempotency_key = $2 AND i.actor_user_id IS NOT DISTINCT FROM $3::uuid - AND ($4::boolean = false OR (t.created_by = $5 AND t.source = 'manual')) + AND ($4::boolean = false OR t.assigned_user_id = $5) FOR UPDATE`, [ context.organizationId, @@ -4059,16 +4072,16 @@ export class TaskService { `INSERT INTO tasks (organization_id, task_id, source, original_text_ciphertext, business_route_id, input_contract_version, parser_mode, parser_config_revision, parser_engine_affinity, - status, stage, message, created_by, idempotency_key) + status, stage, message, created_by, assigned_user_id, idempotency_key) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, - $10, $11, $12, $13, $14) + $10, $11, $12, $13, $14, $15) RETURNING *`, [ context.organizationId, taskId, source, encryptText(this.config, normalized), parserSnapshot.routeId, parserSnapshot.inputContractVersion, parserSnapshot.mode, parserSnapshot.revision, parserSnapshot.engineAffinity, intake.status, intake.stage, intake.message, - context.userId || null, idempotencyKey || null + context.userId || null, context.userId || null, idempotencyKey || null ] ); const row = inserted.rows[0] as Record; @@ -4186,7 +4199,7 @@ export class TaskService { FROM tasks WHERE organization_id = $1 AND task_id = $2 AND archived_at IS NULL - AND ($3::boolean = false OR (created_by = $4 AND source = 'manual'))`, + AND ($3::boolean = false OR assigned_user_id = $4)`, [context.organizationId, requestedTaskId, this.isOwnerRestrictedUser(context), context.userId || null] ); if (target.rowCount) preflightRouteId = businessRouteById(target.rows[0].business_route_id)?.routeId || null; @@ -4200,7 +4213,7 @@ export class TaskService { AND t.archived_at IS NULL AND s.conversation_id = $2 AND ($3::uuid IS NULL OR t.channel_id = $3::uuid) - AND ($4::boolean = false OR (t.created_by = $5 AND t.source = 'manual')) + AND ($4::boolean = false OR t.assigned_user_id = $5) ORDER BY t.updated_at DESC`, [ context.organizationId, @@ -4224,12 +4237,15 @@ export class TaskService { const outcome = await withTransaction(this.config, async (client) => { if (channelId) { const channel = await client.query( - `SELECT id FROM user_channels - WHERE organization_id = $1 AND id = $2 + `SELECT id, owner_user_id FROM user_channels + WHERE organization_id = $1 AND id = $2 AND enabled = true FOR SHARE`, [context.organizationId, channelId] ); if (!channel.rowCount) throw new TaskError('channel_not_found', '用户渠道不存在。', 404); + if (!context.userId || text(channel.rows[0].owner_user_id) !== context.userId) { + throw new TaskError('channel_owner_mismatch', 'AgentBus 渠道与任务归属账号不一致,已拒绝接收。', 403); + } } if (idempotencyKey) { await this.lockIdempotencyKey( @@ -4258,7 +4274,7 @@ export class TaskService { `SELECT id, task_id, channel_id FROM tasks WHERE organization_id = $1 AND task_id = $2 - AND ($3::boolean = false OR (created_by = $4 AND source = 'manual')) + AND ($3::boolean = false OR assigned_user_id = $4) FOR UPDATE`, [ context.organizationId, @@ -4301,7 +4317,7 @@ export class TaskService { JOIN agent_sessions s ON s.task_id = t.id WHERE t.organization_id = $1 AND t.task_id = $2 AND t.archived_at IS NULL - AND ($3::boolean = false OR (t.created_by = $4 AND t.source = 'manual')) + AND ($3::boolean = false OR t.assigned_user_id = $4) FOR UPDATE OF t, s`, [context.organizationId, requestedTaskId, this.isOwnerRestrictedUser(context), context.userId || null] ); @@ -4336,7 +4352,7 @@ export class TaskService { AND t.archived_at IS NULL AND s.conversation_id = $2 AND ($3::uuid IS NULL OR t.channel_id = $3::uuid) - AND ($4::boolean = false OR (t.created_by = $5 AND t.source = 'manual')) + AND ($4::boolean = false OR t.assigned_user_id = $5) ORDER BY t.updated_at DESC FOR UPDATE OF t, s`, [ @@ -4371,15 +4387,15 @@ export class TaskService { `INSERT INTO tasks (organization_id, task_id, source, channel_id, original_text_ciphertext, business_route_id, input_contract_version, parser_mode, parser_config_revision, parser_engine_affinity, - status, stage, message, created_by) + status, stage, message, created_by, assigned_user_id) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, - $11, $12, $13, $14) + $11, $12, $13, $14, $15) RETURNING *`, [ context.organizationId, taskId, source, channelId || null, encryptText(this.config, normalizedMessage), parserSnapshot.routeId, parserSnapshot.inputContractVersion, parserSnapshot.mode, parserSnapshot.revision, parserSnapshot.engineAffinity, - intake.status, intake.stage, intake.message, context.userId || null + intake.status, intake.stage, intake.message, context.userId || null, context.userId || null ] ); const taskRow = inserted.rows[0] as Record; @@ -4539,6 +4555,7 @@ export class TaskService { const result = await getPool(this.config).query( `SELECT t.*, uc.display_name AS channel_name, s.conversation_id, creator.id AS creator_id, creator.username AS creator_username, + assignee.id AS assignee_id, assignee.username AS assignee_username, archiver.id AS archived_by_id, archiver.username AS archived_by_username, s.status AS agent_session_status, s.current_turn AS agent_session_turn, @@ -4559,6 +4576,7 @@ export class TaskService { LEFT JOIN user_channels uc ON uc.id = t.channel_id LEFT JOIN agent_sessions s ON s.task_id = t.id LEFT JOIN users creator ON creator.id = t.created_by + LEFT JOIN users assignee ON assignee.id = t.assigned_user_id LEFT JOIN users archiver ON archiver.id = t.archived_by LEFT JOIN LATERAL ( SELECT id, review_status, diff_summary, program_result_ciphertext, ai_result_ciphertext @@ -4568,7 +4586,7 @@ export class TaskService { LIMIT 1 ) pd ON true WHERE t.organization_id = $1 AND t.task_id = $2 - AND ($3::boolean = false OR (t.created_by = $4 AND t.source = 'manual'))`, + AND ($3::boolean = false OR t.assigned_user_id = $4)`, [organizationId, taskId, isTaskOwnerRestricted(access?.role), access?.userId || null] ); if (!result.rowCount) throw new TaskError('task_not_found', '任务不存在。', 404); @@ -4597,7 +4615,7 @@ export class TaskService { const task = await getPool(this.config).query( `SELECT id FROM tasks WHERE organization_id = $1 AND task_id = $2 - AND ($3::boolean = false OR (created_by = $4 AND source = 'manual'))`, + AND ($3::boolean = false OR assigned_user_id = $4)`, [context.organizationId, taskId, this.isOwnerRestrictedUser(context), context.userId || null] ); if (!task.rowCount) throw new TaskError('task_not_found', '任务不存在。', 404); @@ -5199,7 +5217,7 @@ export class TaskService { const authorized = await getPool(this.config).query( `SELECT 1 FROM tasks WHERE organization_id = $1 AND task_id = $2 - AND ($3::boolean = false OR (created_by = $4 AND source = 'manual'))`, + AND ($3::boolean = false OR assigned_user_id = $4)`, [organizationId, taskId, isTaskOwnerRestricted(access?.role), access?.userId || null] ); if (!authorized.rowCount) throw new TaskError('artifact_not_found', '附件不存在或无权访问。', 404); @@ -5246,16 +5264,21 @@ export class TaskService { search?: string; includeTotal?: boolean; archive?: 'active' | 'archived' | 'all'; + assignedToUserId?: string; access?: TaskAccessScope; } = {} ): Promise { const status = text(options.status).trim(); const search = text(options.search).trim(); - const params: unknown[] = [organizationId]; + const params: unknown[] = [organizationId]; let where = 't.organization_id = $1'; if (isTaskOwnerRestricted(options.access?.role)) { params.push(options.access?.userId || ''); - where += ` AND t.created_by = $${params.length} AND t.source = 'manual'`; + where += ` AND t.assigned_user_id = $${params.length}`; + } + if (options.assignedToUserId) { + params.push(options.assignedToUserId); + where += ` AND t.assigned_user_id = $${params.length}`; } if (options.archive === 'archived') where += ' AND t.archived_at IS NOT NULL'; else if (options.archive !== 'all') where += ' AND t.archived_at IS NULL'; @@ -5313,6 +5336,7 @@ export class TaskService { `SELECT t.id, t.organization_id, t.task_id, t.source, t.channel_id, uc.display_name AS channel_name, creator.id AS creator_id, creator.username AS creator_username, + assignee.id AS assignee_id, assignee.username AS assignee_username, archiver.id AS archived_by_id, archiver.username AS archived_by_username, t.summary, t.parse_response, t.execution_result, t.status, t.stage, t.message, t.error, @@ -5340,6 +5364,7 @@ export class TaskService { LEFT JOIN user_channels uc ON uc.id = t.channel_id LEFT JOIN agent_sessions s ON s.task_id = t.id LEFT JOIN users creator ON creator.id = t.created_by + LEFT JOIN users assignee ON assignee.id = t.assigned_user_id LEFT JOIN users archiver ON archiver.id = t.archived_by WHERE ${where} ORDER BY t.created_at DESC, t.id DESC LIMIT $${limitParam} OFFSET $${offsetParam}`, @@ -6008,6 +6033,9 @@ export class TaskService { async confirmTask(context: TaskContext, taskId: string): Promise { const outcome = await withTransaction(this.config, async (client) => { const row = await this.lockTaskForAccess(client, context, taskId); + if (!context.userId || text(row.assigned_user_id) !== context.userId) { + throw new TaskError('task_execution_assignee_mismatch', '任务不属于当前账号,禁止确认 ERP 执行。', 403); + } await this.assertTaskCreatorBusinessAuthorizationInTransaction(client, row, context.requestId); if (row.status !== 'awaiting_confirmation') { throw new TaskError('invalid_transition', `任务当前状态为 ${row.status},不能确认 Agent 结果并提交下一步。`); @@ -6072,6 +6100,9 @@ export class TaskService { ); if (!organization.rowCount) throw new TaskError('organization_not_found', '组织不存在。', 404); const row = await this.lockTaskForAccess(client, context, taskId); + if (!context.userId || text(row.assigned_user_id) !== context.userId) { + throw new TaskError('task_execution_assignee_mismatch', '任务不属于当前账号的云电脑,禁止领取执行。', 403); + } await this.assertTaskCreatorBusinessAuthorizationInTransaction(client, row, context.requestId); await this.requireBrowserConnection(client, context, connectionId); const existingAttempt = await client.query( @@ -6113,7 +6144,7 @@ export class TaskService { throw new TaskError('execution_attempt_exists', '任务已有 ERP 执行记录,已阻止再次下发。'); } const activeExecutions = await client.query( - `SELECT t.id, t.task_id, t.status, t.created_by, t.source, a.id AS execution_id + `SELECT t.id, t.task_id, t.status, t.assigned_user_id, a.id AS execution_id FROM tasks t JOIN task_attempts a ON a.task_id = t.id AND a.phase = 'erp' WHERE t.organization_id = $1 @@ -6133,25 +6164,25 @@ export class TaskService { AND status = 'confirmed' AND handoff_status = 'awaiting_handoff' AND ( - source <> 'manual' - OR EXISTS ( - SELECT 1 FROM users creator - WHERE creator.id = tasks.created_by - AND creator.organization_id = tasks.organization_id - AND creator.is_active = true - AND creator.role = 'admin' + EXISTS ( + SELECT 1 FROM users assignee + WHERE assignee.id = tasks.assigned_user_id + AND assignee.organization_id = tasks.organization_id + AND assignee.is_active = true + AND assignee.role = 'admin' + AND tasks.source = 'manual' ) OR EXISTS ( SELECT 1 - FROM users creator + FROM users assignee JOIN user_business_route_authorizations route_grant - ON route_grant.organization_id = creator.organization_id - AND route_grant.user_id = creator.id + ON route_grant.organization_id = assignee.organization_id + AND route_grant.user_id = assignee.id AND route_grant.route_id = tasks.business_route_id - WHERE creator.id = tasks.created_by - AND creator.organization_id = tasks.organization_id - AND creator.is_active = true - AND creator.role IN ('team_lead', 'user') + WHERE assignee.id = tasks.assigned_user_id + AND assignee.organization_id = tasks.organization_id + AND assignee.is_active = true + AND assignee.role IN ('team_lead', 'user') ) ) ORDER BY created_at ASC, id ASC @@ -6166,8 +6197,7 @@ export class TaskService { if (activeOther || queueIndex > 0) { const queuePosition = queueIndex + 1; const activeTaskId = activeOther && canAccessTask(context, { - createdBy: text(activeOther.created_by) || null, - source: normalizeTaskSource(activeOther.source) + assignedUserId: text(activeOther.assigned_user_id) || null }) ? text(activeOther.task_id) : null; await this.audit(client, context, 'task.browser_queued', taskId, { connection_id: connectionId, @@ -6276,6 +6306,9 @@ export class TaskService { try { const outcome = await withTransaction(this.config, async (client) => { const row = await this.lockTaskForAccess(client, context, taskId); + if (!context.userId || text(row.assigned_user_id) !== context.userId) { + throw new TaskError('task_execution_assignee_mismatch', '任务不属于当前账号的云电脑,禁止提交执行结果。', 403); + } await this.requireBrowserConnection(client, context, connectionId); const attempt = await client.query( `SELECT * FROM task_attempts @@ -6886,7 +6919,7 @@ export class TaskService { `SELECT * FROM tasks WHERE organization_id = $1 AND task_id = ANY($2::text[]) - AND ($3::boolean = false OR (created_by = $4 AND source = 'manual')) + AND ($3::boolean = false OR assigned_user_id = $4) ORDER BY task_id FOR UPDATE`, [context.organizationId, normalizedTaskIds, this.isOwnerRestrictedUser(context), context.userId || null] @@ -6999,30 +7032,116 @@ export class TaskService { return this.getTask(context.organizationId, taskId, context); } - async heartbeat(context: TaskContext, connectionId: string, extensionVersion: string, metadata: Record = {}): Promise { - const result = await getPool(this.config).query( - `INSERT INTO browser_connections - (organization_id, user_id, connection_id, extension_version, status, last_seen_at, metadata) - VALUES ($1, $2, $3, $4, 'connected', now(), $5) - ON CONFLICT (organization_id, connection_id) - DO UPDATE SET extension_version = EXCLUDED.extension_version, - status = 'connected', last_seen_at = now(), metadata = EXCLUDED.metadata - WHERE browser_connections.user_id = EXCLUDED.user_id - RETURNING id`, - [context.organizationId, context.userId, connectionId, extensionVersion || null, metadata] - ); - if (!result.rowCount) { - throw new TaskError('browser_connection_not_owned', '浏览器连接已绑定其他账号,请重新生成连接标识。', 403); - } + async heartbeat( + context: TaskContext, + connectionId: string, + extensionVersion: string, + metadata: Record = {}, + routing: { erpAccount?: string; erpAccountMatched?: boolean } = {} + ): Promise<{ execution_ready: boolean; erp_account_matched: boolean; worker_connection_id: string }> { + return withTransaction(this.config, async (client) => { + const account = await client.query( + `SELECT id, role, is_active, erp_account + FROM users + WHERE organization_id = $1 AND id = $2 + FOR UPDATE`, + [context.organizationId, context.userId] + ); + if (!account.rowCount || !databaseBoolean(account.rows[0].is_active)) { + throw new TaskError('browser_account_inactive', '当前平台账号不存在或已停用。', 403); + } + const accountRow = account.rows[0] as Record; + const expectedErpAccount = text(accountRow.erp_account); + const reportedErpAccount = text(routing.erpAccount); + const isAdmin = text(accountRow.role) === 'admin'; + const erpAccountMatched = isAdmin + ? true + : Boolean( + expectedErpAccount + && routing.erpAccountMatched === true + && reportedErpAccount.toLocaleLowerCase() === expectedErpAccount.toLocaleLowerCase() + ); + const executionReady = isAdmin || erpAccountMatched; + + if (executionReady) { + const competing = await client.query( + `SELECT connection_id + FROM browser_connections + WHERE organization_id = $1 + AND user_id = $2 + AND connection_id <> $3 + AND status = 'connected' + AND last_seen_at >= now() - interval '90 seconds' + FOR UPDATE`, + [context.organizationId, context.userId, connectionId] + ); + if (competing.rowCount) { + throw new TaskError( + 'browser_worker_conflict', + '该平台账号已有另一台在线云电脑作为执行端;请退出旧云电脑或等待 90 秒后再连接。', + 409, + { active_connection_id: text(competing.rows[0].connection_id) } + ); + } + + await client.query( + `UPDATE browser_connections + SET status = 'superseded', erp_account_verified = false + WHERE organization_id = $1 + AND user_id = $2 + AND connection_id <> $3 + AND status = 'connected'`, + [context.organizationId, context.userId, connectionId] + ); + } + + const safeMetadata = { + ...metadata, + erp_account_configured: Boolean(expectedErpAccount), + erp_account_matched: erpAccountMatched + }; + delete (safeMetadata as Record).erp_account; + const result = await client.query( + `INSERT INTO browser_connections + (organization_id, user_id, connection_id, extension_version, status, last_seen_at, metadata, + erp_account_verified, erp_account_fingerprint) + VALUES ($1, $2, $3, $4, $5, now(), $6, $7, $8) + ON CONFLICT (organization_id, connection_id) + DO UPDATE SET extension_version = EXCLUDED.extension_version, + status = EXCLUDED.status, last_seen_at = now(), metadata = EXCLUDED.metadata, + erp_account_verified = EXCLUDED.erp_account_verified, + erp_account_fingerprint = EXCLUDED.erp_account_fingerprint + WHERE browser_connections.user_id = EXCLUDED.user_id + RETURNING id`, + [ + context.organizationId, + context.userId, + connectionId, + extensionVersion || null, + executionReady ? 'connected' : 'identity_mismatch', + safeMetadata, + erpAccountMatched, + expectedErpAccount ? sha256Text(expectedErpAccount.toLocaleLowerCase()) : null + ] + ); + if (!result.rowCount) { + throw new TaskError('browser_connection_not_owned', '浏览器连接已绑定其他账号,请重新生成连接标识。', 403); + } + return { + execution_ready: executionReady, + erp_account_matched: erpAccountMatched, + worker_connection_id: connectionId + }; + }); } async eventsSince(organizationId: string, since = 0, access?: TaskAccessScope): Promise { const result = await getPool(this.config).query( - `SELECT e.id, e.organization_id, t.task_id, t.created_by, t.source, + `SELECT e.id, e.organization_id, t.task_id, t.assigned_user_id, t.source, e.status, e.stage, e.message, e.payload, e.created_at FROM task_events e JOIN tasks t ON t.id = e.task_id WHERE e.organization_id = $1 AND e.id > $2 - AND ($3::boolean = false OR (t.created_by = $4 AND t.source = 'manual')) + AND ($3::boolean = false OR t.assigned_user_id = $4) ORDER BY e.id ASC LIMIT 500`, [organizationId, since, isTaskOwnerRestricted(access?.role), access?.userId || null] ); @@ -7035,7 +7154,7 @@ export class TaskService { message: text(row.message), payload: jsonObject(row.payload), created_at: new Date(String(row.created_at)).toISOString(), - owner_user_id: text(row.created_by) || null, + owner_user_id: text(row.assigned_user_id) || null, task_source: normalizeTaskSource(row.source) })); } diff --git a/control-plane/test/account-authorization.test.ts b/control-plane/test/account-authorization.test.ts index 3644c91..133f5ee 100644 --- a/control-plane/test/account-authorization.test.ts +++ b/control-plane/test/account-authorization.test.ts @@ -55,6 +55,32 @@ test('business authorization migration adds a fail-closed per-user allowlist for assert.doesNotMatch(sql, /INSERT INTO user_business_route_authorizations[\s\S]+SELECT[\s\S]+FROM users/i); }); +test('AgentBus account-worker migration adds fail-closed channel, task, browser, and ERP identity ownership', async () => { + const sql = await source('../migrations/018_agentbus_account_workers.sql'); + assert.match(sql, /ADD COLUMN IF NOT EXISTS erp_account text/); + assert.match(sql, /users_org_erp_account_unique_idx/); + assert.match(sql, /ADD COLUMN IF NOT EXISTS owner_user_id uuid/); + assert.match(sql, /FOREIGN KEY \(organization_id, owner_user_id\)[\s\S]+REFERENCES users \(organization_id, id\)/); + assert.match(sql, /user_channels_owner_unique_idx/); + assert.match(sql, /ADD COLUMN IF NOT EXISTS assigned_user_id uuid/); + assert.match(sql, /FOREIGN KEY \(organization_id, assigned_user_id\)[\s\S]+REFERENCES users \(organization_id, id\)/); + assert.match(sql, /source = 'manual'[\s\S]+created_by IS NOT NULL/); + assert.match(sql, /erp_account_verified boolean NOT NULL DEFAULT false/); + assert.match(sql, /browser_connections_active_user_unique_idx/); + assert.match(sql, /status = 'superseded'/); + assert.match(sql, /owner_user_id IS NULL[\s\S]+enabled = true/); +}); + +test('AgentBus channel keys and owners are unique so one inbound identity cannot fan out to multiple employees', async () => { + const channels = await source('../src/agentbus-channels.ts'); + assert.match(channels, /requireAssignableOwner/); + assert.match(channels, /requireUniqueAgentBusKey/); + assert.match(channels, /pg_advisory_xact_lock/); + assert.match(channels, /sha256Text\(agentbusKey\)/); + assert.match(channels, /channel_key_conflict/); + assert.match(channels, /channel_owner_conflict/); +}); + test('account lifecycle is administrator-gated and protects passwords, sessions, and the last administrator', async () => { const [auth, server] = await Promise.all([ source('../src/auth.ts'), @@ -71,6 +97,9 @@ test('account lifecycle is administrator-gated and protects passwords, sessions, assert.doesNotMatch(auth, /password\.length < 12|12—512/); assert.match(auth, /account\.password_reset/); assert.match(auth, /account\.password_changed/); + assert.match(auth, /function validateAccountRouting/); + assert.match(auth, /admin_erp_account_forbidden/); + assert.match(auth, /erp_account_conflict/); assert.match(server, /app\.get\('\/api\/accounts'[\s\S]+requireAdminSession\(request\)/); assert.match(server, /app\.post\('\/api\/accounts'[\s\S]+requireAdminMutationSession\(request\)/); assert.match(server, /app\.get\('\/api\/audit'[\s\S]+requireAdminSession\(request\)/); @@ -183,7 +212,7 @@ test('ordinary task access is enforced across reads, mutations, artifacts, event source('../src/task-service.ts'), source('../src/server.ts') ]); - assert.match(tasks, /created_by = \$4 AND source = 'manual'/); + assert.match(tasks, /assigned_user_id = \$4/); assert.match(tasks, /private async lockTaskForAccess/); for (const mutation of ['reparseTaskWithAi', 'confirmTask', 'claimForBrowser', 'recordExecutionResult', 'cancelTask']) { const start = tasks.indexOf(`async ${mutation}(`); @@ -191,11 +220,19 @@ test('ordinary task access is enforced across reads, mutations, artifacts, event const body = tasks.slice(start, start + 20_000); assert.match(body, /lockTaskForAccess\(/, `${mutation} uses the task access lock`); } - assert.match(tasks, /async getTaskArtifact[\s\S]+created_by = \$4 AND source = 'manual'/); - assert.match(tasks, /async eventsSince[\s\S]+t\.created_by = \$4 AND t\.source = 'manual'/); + assert.match(tasks, /async getTaskArtifact[\s\S]+assigned_user_id = \$4/); + assert.match(tasks, /async eventsSince[\s\S]+t\.assigned_user_id = \$4/); assert.match(tasks, /async getTaskInputHistory[\s\S]+actor_user_id/); - assert.match(tasks, /WHERE organization_id = \$1 AND user_id = \$2 AND connection_id = \$3/); + assert.match(tasks, /connection\.organization_id = \$1[\s\S]+connection\.user_id = \$2[\s\S]+connection\.connection_id = \$3/); assert.match(tasks, /WHERE browser_connections\.user_id = EXCLUDED\.user_id/); + assert.match(tasks, /browser_worker_conflict/); + assert.match(tasks, /identity_mismatch/); + assert.match(tasks, /erp_account_mismatch/); + assert.match(tasks, /task_execution_assignee_mismatch/); + assert.match(tasks, /assignee: publicActor\(row\.assignee_id, row\.assignee_username\)/); + const confirmation = tasks.slice(tasks.indexOf('async confirmTask('), tasks.indexOf('async claimForBrowser(')); + assert.match(confirmation, /assigned_user_id/); + assert.match(confirmation, /task_execution_assignee_mismatch/); assert.match(tasks, /i\.actor_user_id IS NOT DISTINCT FROM \$3::uuid/); assert.match(tasks, /private async lockIdempotencyKey/); assert.match(tasks, /pg_advisory_xact_lock/); @@ -215,6 +252,8 @@ test('operator UI exposes role-aware accounts, executive drill-through, original assert.match(index, /href="\/operations-dashboard"/); assert.match(index, /id="passwordChangeForm"/); assert.match(index, /id="accountForm"/); + assert.match(index, /id="accountErpAccount"/); + assert.match(index, /id="channelOwnerUserId"/); assert.doesNotMatch(index, /accountMustChangePassword|首次登录必须修改密码|minlength="12"|12—512/); assert.match(index, /id="accountAuthorizationPanel"/); assert.match(index, /id="accountAuthorizationTypes"/); @@ -240,6 +279,11 @@ test('operator UI exposes role-aware accounts, executive drill-through, original assert.match(app, /\/api\/tasks\/\$\{encodeURIComponent\(taskId\)\}\/input-history/); assert.match(app, /创建人与原始输入审计/); assert.match(app, /function renderAccountAuthorizationPanel/); + assert.match(app, /function renderChannelOwnerOptions/); + assert.match(app, /function taskAssignedToCurrentAccount/); + assert.match(app, /expected_erp_account/); + assert.match(app, /executable_by=me/); + assert.match(app, /const candidates = Array\.isArray\(result\.tasks\) \? result\.tasks : \[\]/); assert.match(app, /\/business-authorizations/); assert.match(app, /当前默认不能执行任何业务/); assert.match(app, /function canViewOperationsDashboard/); diff --git a/control-plane/test/agentbus.test.ts b/control-plane/test/agentbus.test.ts index 4789c8c..67d6578 100644 --- a/control-plane/test/agentbus.test.ts +++ b/control-plane/test/agentbus.test.ts @@ -145,6 +145,11 @@ function publicChannelFixture(overrides: Partial = {}): P display_name: '外部用户 A', external_user_ref: 'external-user-a', agentbus_bot_address: 'bot:test:listener', + owner_user_id: 'user-a', + owner_username: 'employee-a', + owner_role: 'user', + owner_erp_account: 'ERP-A', + routing_ready: true, enabled: true, status: 'disabled', key_configured: true, @@ -1199,7 +1204,9 @@ test('durable channel listener persists route and resends accepted/result delive displayName: '外部用户 A', wsUrl: 'wss://mesh.nianxx.cn/ws', wsToken: 'channel-ws-token', - botAddress: 'bot:channel-a:listener' + botAddress: 'bot:channel-a:listener', + ownerUserId: 'user-a', + ownerRole: 'user' } }); @@ -1228,6 +1235,8 @@ test('durable channel listener persists route and resends accepted/result delive await new Promise((resolve) => setTimeout(resolve, 50)); } assert.equal(receivedContext.channelId, 'channel-1'); + assert.equal(receivedContext.userId, 'user-a'); + assert.equal(receivedContext.role, 'user'); assert.equal(receivedInput.channelId, 'channel-1'); assert.deepEqual(receivedInput.agentBusRoute, { inboundFrameId: 'channel-durable-1', @@ -1399,7 +1408,9 @@ test('durable roster lifecycle assigns the final result only to the attachment f displayName: '外部用户 A', wsUrl: 'wss://mesh.nianxx.cn/ws', wsToken: 'channel-ws-token', - botAddress: 'bot:channel-a:listener' + botAddress: 'bot:channel-a:listener', + ownerUserId: 'user-a', + ownerRole: 'user' } }); t.after(() => listener.stop()); @@ -1555,7 +1566,9 @@ test('durable channel persists and sends an attachment rejection result while th displayName: '外部用户 A', wsUrl: 'wss://mesh.nianxx.cn/ws', wsToken: 'channel-ws-token', - botAddress: 'bot:channel-a:listener' + botAddress: 'bot:channel-a:listener', + ownerUserId: 'user-a', + ownerRole: 'user' }, logger: { info(metadata, message) { diff --git a/control-plane/test/control-plane.test.ts b/control-plane/test/control-plane.test.ts index 07b1e64..12e395d 100644 --- a/control-plane/test/control-plane.test.ts +++ b/control-plane/test/control-plane.test.ts @@ -47,18 +47,18 @@ test('message routing starts a new session for a business directive, not for a s test('task access contract isolates users and team leads while preserving administrator and worker access', () => { const cases = [ - { name: 'administrator sees another user manual task', access: { userId: 'admin', role: 'admin' as const }, createdBy: 'user-a', source: 'manual' as const, allowed: true }, - { name: 'trusted worker sees AgentBus task', access: { userId: '', role: undefined }, createdBy: null, source: 'agentbus' as const, allowed: true }, - { name: 'team lead sees own manual task', access: { userId: 'lead-a', role: 'team_lead' as const }, createdBy: 'lead-a', source: 'manual' as const, allowed: true }, - { name: 'team lead cannot use the normal task path for another manual task', access: { userId: 'lead-a', role: 'team_lead' as const }, createdBy: 'user-b', source: 'manual' as const, allowed: false }, - { name: 'team lead cannot use the normal task path for AgentBus work', access: { userId: 'lead-a', role: 'team_lead' as const }, createdBy: 'lead-a', source: 'agentbus' as const, allowed: false }, - { name: 'ordinary user sees own manual task', access: { userId: 'user-a', role: 'user' as const }, createdBy: 'user-a', source: 'manual' as const, allowed: true }, - { name: 'ordinary user cannot see another manual task', access: { userId: 'user-a', role: 'user' as const }, createdBy: 'user-b', source: 'manual' as const, allowed: false }, - { name: 'ordinary user cannot see AgentBus task', access: { userId: 'user-a', role: 'user' as const }, createdBy: 'user-a', source: 'agentbus' as const, allowed: false }, - { name: 'ordinary user without an actor cannot see a task', access: { userId: '', role: 'user' as const }, createdBy: '', source: 'manual' as const, allowed: false } + { name: 'administrator can inspect another assigned task', access: { userId: 'admin', role: 'admin' as const }, assignedUserId: 'user-a', allowed: true }, + { name: 'trusted worker can inspect an unassigned task', access: { userId: '', role: undefined }, assignedUserId: null, allowed: true }, + { name: 'team lead sees own manual task', access: { userId: 'lead-a', role: 'team_lead' as const }, assignedUserId: 'lead-a', allowed: true }, + { name: 'team lead cannot use the normal task path for another task', access: { userId: 'lead-a', role: 'team_lead' as const }, assignedUserId: 'user-b', allowed: false }, + { name: 'team lead sees own AgentBus work', access: { userId: 'lead-a', role: 'team_lead' as const }, assignedUserId: 'lead-a', allowed: true }, + { name: 'ordinary user sees own manual task', access: { userId: 'user-a', role: 'user' as const }, assignedUserId: 'user-a', allowed: true }, + { name: 'ordinary user cannot see another task', access: { userId: 'user-a', role: 'user' as const }, assignedUserId: 'user-b', allowed: false }, + { name: 'ordinary user sees own AgentBus task', access: { userId: 'user-a', role: 'user' as const }, assignedUserId: 'user-a', allowed: true }, + { name: 'ordinary user without an actor cannot see a task', access: { userId: '', role: 'user' as const }, assignedUserId: '', allowed: false } ]; for (const item of cases) { - assert.equal(canAccessTask(item.access, { createdBy: item.createdBy, source: item.source }), item.allowed, item.name); + assert.equal(canAccessTask(item.access, { assignedUserId: item.assignedUserId }), item.allowed, item.name); } assert.equal(isTaskOwnerRestricted('admin'), false); assert.equal(isTaskOwnerRestricted('team_lead'), true); @@ -86,8 +86,14 @@ test('business route authorization is an explicit allowlist for team leads and o role: 'user', source: 'manual', routeId: null, authorizedRouteIds: [routeId] }), false, 'unclassified manual input fails closed for non-administrators'); assert.equal(canExecuteBusinessRoute({ - role: undefined, source: 'agentbus', routeId: null, authorizedRouteIds: [] - }), true, 'trusted AgentBus intake retains its separate administrator-controlled boundary'); + role: 'user', source: 'agentbus', routeId, authorizedRouteIds: [routeId] + }), true, 'bound AgentBus intake uses the employee route allowlist'); + assert.equal(canExecuteBusinessRoute({ + role: undefined, source: 'agentbus', routeId, authorizedRouteIds: [routeId] + }), false, 'unbound AgentBus intake fails closed'); + assert.equal(canExecuteBusinessRoute({ + role: 'admin', source: 'agentbus', routeId, authorizedRouteIds: [routeId] + }), false, 'administrators cannot be AgentBus execution owners'); }); test('field encryption round-trips without storing plaintext', () => { @@ -311,7 +317,7 @@ test('control plane requires the latest durable task-outcome migration before re const { readFile } = await import('node:fs/promises'); const db = await readFile(new URL('../src/db.ts', import.meta.url), 'utf8'); const server = await readFile(new URL('../src/server.ts', import.meta.url), 'utf8'); - assert.equal(REQUIRED_SCHEMA_VERSION, '017_user_business_route_authorizations'); + assert.equal(REQUIRED_SCHEMA_VERSION, '018_agentbus_account_workers'); assert.match(db, /schema_migrations/); assert.match(db, /databaseReadiness/); assert.match(db, /assertDatabaseSchema/); @@ -1226,7 +1232,7 @@ test('operator page has a login gate and uses the durable task API', async () => assert.match(index, /id="loginPanel"/); assert.match(index, /id="workbench"[^>]*hidden/); assert.match(index, /styles\.css\?v=20260902-dashboard-mobile-share-1/); - assert.match(index, /app\.js\?v=20260902-dashboard-mobile-share-1/); + assert.match(index, /app\.js\?v=20260902-agentbus-account-routing-1/); assert.match(index, /id="statusDetailsPopover"/); assert.match(index, /id="statusDetailsRefresh"/); assert.match(app, /apiRequest\(`\/api\/tasks\?\$\{params\.toString\(\)\}`/); @@ -1430,7 +1436,7 @@ test('operator page has a login gate and uses the durable task API', async () => assert.doesNotMatch(app, /task-stage-index|task-stage-current/); assert.ok(app.indexOf('/claim') < app.indexOf("sendToExtension('CREATE_TASK'"), 'server claim must precede extension dispatch'); assert.doesNotMatch(app, /syncPendingTasks|>重交 taskAssignedToCurrentAccount\(task\) && isTaskPollable\(task\)\)/); assert.match(app, /execution_id: executionId/); assert.doesNotMatch(bridge, /async function upsertBusinessTask/); assert.match(bridge, /task: currentBusinessTask/); diff --git a/dist/ltjt-order-assistant-0.5.164.zip b/dist/ltjt-order-assistant-0.5.164.zip new file mode 100644 index 0000000000000000000000000000000000000000..cb085e23716df35318542308ea14b79aa0d61b82 GIT binary patch literal 213669 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∾&ZUPUjIvk`70@bha($YdT7lPJ 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[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 { @@ -1926,7 +1926,7 @@ test('delete guard distinguishes independent, shared child, and shared parent ro for (const operation of operations) assert.equal(plans.validateOperation(operation).ok, true, plans.validateOperation(operation).blockers.join('; ')); const mapping = JSON.parse(await readFile(new URL('../mappings/lifecycle.mapping.json', import.meta.url), 'utf8')); - assert.equal(mapping.current_extension_version, '0.5.163'); + assert.equal(mapping.current_extension_version, '0.5.164'); assert.equal( mapping.updates.order_update_independent.field_mapped_pending_live_validation['pax.child_no_bed'], 'ertrenshu' @@ -2295,7 +2295,8 @@ test('schema and browser adapters contain the v2 safety fields and no confirm ov assert.match(background, /if \(report\.manual_review_required === true\) return 'execution_uncertain'/); assert.match(background, /async function readErpSessionStatus/); assert.match(background, /message\?\.type === 'LTJT_ERP_SESSION_STATUS'/); - assert.match(background, /account_matched: bodyText\.includes\('测试ai员工账号'\)/); + assert.match(background, /account_matched: Boolean\(expectedAccount\) && bodyText\.includes\(expectedAccount\)/); + assert.doesNotMatch(background, /bodyText\.includes\('测试ai员工账号'\)/); assert.match(background, /lifecycle_preflight_ambiguous/); assert.match(background, /lifecycle_frame_discovery_retry_exhausted/); assert.match(background, /lifecycleFrameDiscoveryOnly/); @@ -2308,10 +2309,10 @@ test('schema and browser adapters contain the v2 safety fields and no confirm ov assert.match(inpage, /`ys_danweiid\$\{index\}`, resolvedCustomerId/); assert.doesNotMatch(inpage, /product_customer_source_region|sourceRegionCheck|source_reference/); assert.doesNotMatch(teamBatchInpage, /product_customer_source_region|sourceRegionCheck/); - assert.equal(extensionManifest.version, '0.5.163'); - assert.match(inpage, /version: '0\.5\.163'/); - assert.match(teamBatchInpage, /version: '0\.5\.163'/); - assert.match(platformApp, /REQUIRED_EXTENSION_VERSION = '0\.5\.163'/); + assert.equal(extensionManifest.version, '0.5.164'); + assert.match(inpage, /version: '0\.5\.164'/); + assert.match(teamBatchInpage, /version: '0\.5\.164'/); + assert.match(platformApp, /REQUIRED_EXTENSION_VERSION = '0\.5\.164'/); assert.match(inpage, /function strictIsoDate\(value\)/); assert.match(inpage, /const startDate = strictIsoDate\(controlCanonicalValue\(form, 'riqi0'\)\)/); assert.match(inpage, /const endDate = strictIsoDate\(controlCanonicalValue\(form, 'riqis0'\)\)/);