@lsamien/yaovi 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/build-info.json +44 -3
  2. package/desktop/main.mjs +18 -9
  3. package/dist/assets/{AboutDialog-BW25cB-W.js → AboutDialog-CJ9tCjSs.js} +2 -2
  4. package/dist/assets/AboutDialog-CJ9tCjSs.js.map +1 -0
  5. package/dist/assets/{BotAutomationsView-BoK2R918.js → BotAutomationsView-CsoI3js3.js} +2 -2
  6. package/dist/assets/{BotAutomationsView-BoK2R918.js.map → BotAutomationsView-CsoI3js3.js.map} +1 -1
  7. package/dist/assets/{BotControlSettingsPanel-BbhD2eC0.js → BotControlSettingsPanel-DIuBZlLu.js} +2 -2
  8. package/dist/assets/{BotControlSettingsPanel-BbhD2eC0.js.map → BotControlSettingsPanel-DIuBZlLu.js.map} +1 -1
  9. package/dist/assets/{BotPluginsDialog-DjwFw-lt.js → BotPluginsDialog-BsGszDMH.js} +2 -2
  10. package/dist/assets/{BotPluginsDialog-DjwFw-lt.js.map → BotPluginsDialog-BsGszDMH.js.map} +1 -1
  11. package/dist/assets/{BotSettingsPanel-DLmnL6pH.js → BotSettingsPanel-j3ThYgt2.js} +3 -3
  12. package/dist/assets/{BotSettingsPanel-DLmnL6pH.js.map → BotSettingsPanel-j3ThYgt2.js.map} +1 -1
  13. package/dist/assets/{ComputerPanel-CRIF1YEd.js → ComputerPanel-DdlgOm-B.js} +2 -2
  14. package/dist/assets/{ComputerPanel-CRIF1YEd.js.map → ComputerPanel-DdlgOm-B.js.map} +1 -1
  15. package/dist/assets/{ComputerViewerView-CizYWH2R.js → ComputerViewerView-I3adPI6O.js} +2 -2
  16. package/dist/assets/{ComputerViewerView-CizYWH2R.js.map → ComputerViewerView-I3adPI6O.js.map} +1 -1
  17. package/dist/assets/ConversationsView-CBhzg-9w.css +1 -0
  18. package/dist/assets/{ConversationsView-1igFR9VY.js → ConversationsView-CxVFk5sQ.js} +73 -73
  19. package/dist/assets/ConversationsView-CxVFk5sQ.js.map +1 -0
  20. package/dist/assets/{DevicePairingDialog-orOS4xqu.js → DevicePairingDialog-C307ARt-.js} +2 -2
  21. package/dist/assets/{DevicePairingDialog-orOS4xqu.js.map → DevicePairingDialog-C307ARt-.js.map} +1 -1
  22. package/dist/assets/{LocalVmSettingsPanel-DzM--Lpg.js → LocalVmSettingsPanel-Zs7CiXdf.js} +2 -2
  23. package/dist/assets/{LocalVmSettingsPanel-DzM--Lpg.js.map → LocalVmSettingsPanel-Zs7CiXdf.js.map} +1 -1
  24. package/dist/assets/{ProviderLoginDialog-Bw5KVMfi.js → ProviderLoginDialog-D3mqdAlo.js} +2 -2
  25. package/dist/assets/{ProviderLoginDialog-Bw5KVMfi.js.map → ProviderLoginDialog-D3mqdAlo.js.map} +1 -1
  26. package/dist/assets/{RunnerSettingsPanel-B-fwuBEs.js → RunnerSettingsPanel-DjMGnXGP.js} +2 -2
  27. package/dist/assets/{RunnerSettingsPanel-B-fwuBEs.js.map → RunnerSettingsPanel-DjMGnXGP.js.map} +1 -1
  28. package/dist/assets/{StandaloneDialog-7GkhtRlT.js → StandaloneDialog-BXCK22Dx.js} +2 -2
  29. package/dist/assets/{StandaloneDialog-7GkhtRlT.js.map → StandaloneDialog-BXCK22Dx.js.map} +1 -1
  30. package/dist/assets/{UpdateCheckDialog-DYiM4xBv.js → UpdateCheckDialog-DAY0VEQm.js} +2 -2
  31. package/dist/assets/{UpdateCheckDialog-DYiM4xBv.js.map → UpdateCheckDialog-DAY0VEQm.js.map} +1 -1
  32. package/dist/assets/{UsageView-D1cjS1hy.js → UsageView-DNwG8LAx.js} +2 -2
  33. package/dist/assets/{UsageView-D1cjS1hy.js.map → UsageView-DNwG8LAx.js.map} +1 -1
  34. package/dist/assets/{WorkspaceKnowledgePanel-DcGdLRS1.js → WorkspaceKnowledgePanel-hy_IkIJI.js} +2 -2
  35. package/dist/assets/{WorkspaceKnowledgePanel-DcGdLRS1.js.map → WorkspaceKnowledgePanel-hy_IkIJI.js.map} +1 -1
  36. package/dist/assets/{WorkspaceRoutinesPanel-DLntimKs.js → WorkspaceRoutinesPanel-B7PkBP7x.js} +2 -2
  37. package/dist/assets/{WorkspaceRoutinesPanel-DLntimKs.js.map → WorkspaceRoutinesPanel-B7PkBP7x.js.map} +1 -1
  38. package/dist/assets/{WorkstreamsView-DMoeV8Eu.js → WorkstreamsView-BeE2TRXK.js} +2 -2
  39. package/dist/assets/{WorkstreamsView-DMoeV8Eu.js.map → WorkstreamsView-BeE2TRXK.js.map} +1 -1
  40. package/dist/assets/{index-D0pMYnOi.js → index-D11cgXI9.js} +3 -3
  41. package/dist/assets/{index-D0pMYnOi.js.map → index-D11cgXI9.js.map} +1 -1
  42. package/dist/assets/{pdf-BCisgslg.js → pdf-BjTcV6As.js} +2 -2
  43. package/dist/assets/{pdf-BCisgslg.js.map → pdf-BjTcV6As.js.map} +1 -1
  44. package/dist/index.html +1 -1
  45. package/dist-server/server/botControl.js +13 -1
  46. package/dist-server/server/desktopEnvironments.js +10 -9
  47. package/dist-server/server/desktopHostProtocol.js +1 -1
  48. package/dist-server/server/harness/agentRuntime.js +22 -8
  49. package/dist-server/server/workspaceDialoguePolicy.js +3 -1
  50. package/dist-server/server/workspaceKnowledge.js +25 -16
  51. package/dist-server/server/workspaceMemoryContext.js +75 -19
  52. package/dist-server/server/workspacePrompt.js +11 -4
  53. package/dist-server/server/workspaceRuntime.js +35 -13
  54. package/dist-server/shared/botEnvironment.js +10 -4
  55. package/dist-server/shared/fileTransferEndpoint.mjs +5 -5
  56. package/dist-server/shared/hostPaths.mjs +14 -4
  57. package/package.json +4 -3
  58. package/release.json +3 -3
  59. package/runtime/hermes/hermes_state_messages.py +17 -0
  60. package/runtime/worker.py +34 -1
  61. package/dist/assets/AboutDialog-BW25cB-W.js.map +0 -1
  62. package/dist/assets/ConversationsView-1igFR9VY.js.map +0 -1
  63. package/dist/assets/ConversationsView-h94_iPtv.css +0 -1
  64. package/dist-server/server/harness/context.js +0 -88
@@ -59,7 +59,7 @@ import { createWorkspaceToolLease } from './workspaceToolLease.js';
59
59
  import { WorkspaceTaskCoordinator } from './taskCoordinator.js';
60
60
  import { isDiscussion, discussionRounds } from './workspaceDiscussion.js';
61
61
  import { WorkspaceKnowledge } from './workspaceKnowledge.js';
62
- import { emptyMemoryContext, memoryResetReason, memoryDelta, acknowledgeMemory } from './workspaceMemoryContext.js';
62
+ import { emptyMemoryContext, memoryResetReason, advanceMemory, renderMemoryCorrections } from './workspaceMemoryContext.js';
63
63
  import { WorkspaceCollaboration } from './workspaceCollaboration.js';
64
64
  import { WorkspaceKnowledgeTools, BOT_KNOWLEDGE_RULES } from './workspaceKnowledgeTools.js';
65
65
  export { mentionedAgents } from './workspaceMentions.js';
@@ -406,7 +406,8 @@ export class WorkspaceRuntime extends WorkspaceScheduler {
406
406
  }
407
407
  async deliverForegroundInsertion(owner, conversation, agent, body, targetRunId) {
408
408
  const text = body.content.trim();
409
- if (!text || body.fileIds.length || body.deviceHost || body.mentionIds.length)
409
+ // Insertion only corrects the running task; the message keeps its own source and the run keeps its snapshot.
410
+ if (!text || body.fileIds.length || body.mentionIds.length)
410
411
  return undefined;
411
412
  const foreground = targetRunId ? [this.store.require(owner, 'run', targetRunId)] : this.foregroundMainRuns(owner, conversation.id).filter(run => run.conversationTaskId === body.taskId);
412
413
  const candidates = foreground.filter(run => !run.stopRequested && ['queued', 'running', 'waiting'].includes(run.status))
@@ -467,11 +468,13 @@ export class WorkspaceRuntime extends WorkspaceScheduler {
467
468
  const message = {
468
469
  id: messageId, clientRequestId: body.requestId, conversationId: conversation.id, conversationTaskId: root.conversationTaskId, seq: 0, role: 'user',
469
470
  content: text, reasoning: '', runId, status: 'complete', attachments: [], tools: [], createdAt: now, steered: true, steeredInto: root.id,
471
+ ...(body.deviceHost ? { deviceHost: body.deviceHost } : {}),
470
472
  };
471
473
  this.store.saveMessage(owner, message);
472
474
  const run = {
473
475
  id: runId, conversationId: conversation.id, conversationTaskId: root.conversationTaskId, messageId,
474
476
  mentionIds: [], activeAgentId: agent.id, status: 'complete', round: 0, steeredInto: root.id,
477
+ ...(body.deviceHost ? { deviceHost: body.deviceHost } : {}),
475
478
  createdAt: now, updatedAt: now, authorizationVersion: this.authorizationVersion(owner),
476
479
  };
477
480
  this.store.saveRun(owner, run);
@@ -517,6 +520,7 @@ export class WorkspaceRuntime extends WorkspaceScheduler {
517
520
  mentionIds: [], activeAgentId: conversation.administratorId, status: 'queued', round: 0,
518
521
  createdAt: now, updatedAt: now, authorizationVersion: this.authorizationVersion(owner),
519
522
  routeIntent: 'main_only', steerText: text.trim(),
523
+ ...(trigger.deviceHost ? { deviceHost: trigger.deviceHost } : {}),
520
524
  };
521
525
  this.store.atomic(() => {
522
526
  this.admit(owner, run, conversation, trigger);
@@ -918,10 +922,12 @@ export class WorkspaceRuntime extends WorkspaceScheduler {
918
922
  }
919
923
  const memorySettings = new AgentRuntimeConfiguration(this.store).resolve(owner, agent.id).memory;
920
924
  const memoryEnabled = memorySettings.enabled;
921
- // Legacy context/recall combines self, shared and project memories. Managed
922
- // permissions require explicit scoped tools; native self recall is enforced
923
- // independently by the Harness and remains available there.
924
- const memory = botCapabilities.memory && memoryEnabled && !recovering && !temporaryBackground && !this.botControl.state(owner).enabled ? await this.knowledge.context(owner, agent.id, root.projectId) : emptyMemoryContext('unsupported');
925
+ // Unmanaged context/recall combines self, shared and project memories. Managed
926
+ // Bots receive only scopes their rules explicitly allow (specs/021); tools keep
927
+ // their own review flow, and native self recall stays with the Harness.
928
+ const memoryAllowance = this.botControl.state(owner).enabled ? this.botControl.memoryReadScopes(owner, agent.id, { groupId: this.botControl.permissions.group(owner, run.id), projectId: root.projectId }) : undefined;
929
+ const memorySkip = !botCapabilities.memory ? 'unsupported' : !memoryEnabled ? 'disabled' : recovering ? 'recovering' : temporaryBackground ? 'temporary' : undefined;
930
+ const memory = !memorySkip ? await this.knowledge.context(owner, agent.id, root.projectId, memoryAllowance && { self: memoryAllowance.self.allowed, shared: memoryAllowance.shared.allowed, project: memoryAllowance.project.allowed }) : emptyMemoryContext('unsupported');
925
931
  const memoryStatus = botCapabilities.memory ? 'ready' : 'upgrade_required';
926
932
  if (agent.memoryStatus !== memoryStatus) {
927
933
  agent = { ...agent, memoryStatus };
@@ -929,20 +935,28 @@ export class WorkspaceRuntime extends WorkspaceScheduler {
929
935
  this.store.event(owner, 'agent.changed', this.store.agentSummary(agent));
930
936
  }
931
937
  let binding = this.store.get(owner, 'binding', key);
932
- const memoryReset = !recovering && binding
933
- ? binding.memoryPending ? 'memory_admission_uncertain' : memoryResetReason(memory, binding.memoryState, binding.memoryVersion)
934
- : undefined;
938
+ // Memory content changes are delivered as corrections (user decision 2026-10-22);
939
+ // only a different authorized scope rebuilds the session. An uncertain admission
940
+ // recomputes from the last admitted state, whose ledger is cumulative.
941
+ const memoryReset = !recovering && binding ? memoryResetReason(memory, binding.memoryState) : undefined;
935
942
  const rebuild = this.store.get(owner, 'binding-reset', key);
936
943
  const sourceReset = !!rebuild;
937
944
  const movedRunner = !!rebuild && rebuild.reason !== 'configuration_changed' || !!binding && (binding.deviceHost !== root.deviceHost || binding.computerEnvironmentId !== agent.computerEnvironmentId || binding.runnerId !== target.runner?.id || (binding.execution ?? 'profile') !== chatExecution || !!binding.nativeComputer !== !!target.runner?.nativeComputer);
938
945
  // Rebuild old exclusive-environment sessions once: their 模型执行器 Agent may
939
946
  // still retain restricted native context even after a new tool binding.
947
+ // A rebuilt session still replays recaps of older messages; keep the
948
+ // correction ledger (a different scope starts a fresh one in advanceMemory).
949
+ let carriedMemory;
940
950
  const resetReason = (execution && (binding?.executionRevision !== execution.revision || binding?.executionPolicyVersion !== 2) ? 'execution_environment_changed' : undefined) ?? memoryReset ?? (sourceReset ? rebuild?.reason ?? 'source_changed' : movedRunner ? 'runner_changed' : undefined);
941
951
  if (resetReason) {
942
952
  if (recovering)
943
953
  throw new HttpError(409, '执行节点已变化,不能在另一节点重放原执行', 'runner_target_changed');
944
954
  this.inspector?.record(owner, c.id, { method: 'bot.session-reset', direction: 'event', agentId: agent.id, runId: run.runId, taskId: run.conversationTaskId,
945
955
  data: { reason: resetReason } });
956
+ // Count only sessions actually discarded; a first turn has nothing to reset.
957
+ if (binding)
958
+ this.store.event(owner, 'runtime.event', { botId: agent.id, runId: run.runId, workId: run.id, name: 'session.reset', value: { reason: resetReason } }, c.id);
959
+ carriedMemory = binding?.memoryState;
946
960
  binding = undefined;
947
961
  }
948
962
  let message = run.currentMessageId
@@ -1527,6 +1541,7 @@ export class WorkspaceRuntime extends WorkspaceScheduler {
1527
1541
  const message = error instanceof Error ? error.message : '';
1528
1542
  if (recovering || (!['computer_session_mode_changed', 'model_session_reset_required'].includes(code) && !message.includes('执行方式已改变')))
1529
1543
  throw error;
1544
+ carriedMemory ??= binding?.memoryState;
1530
1545
  binding = undefined;
1531
1546
  opened = await createSession();
1532
1547
  }
@@ -1542,7 +1557,7 @@ export class WorkspaceRuntime extends WorkspaceScheduler {
1542
1557
  binding = {
1543
1558
  toolsBound: recovering ? binding?.toolsBound : undefined,
1544
1559
  memoryVersion: binding?.memoryVersion,
1545
- memoryState: binding?.memoryState,
1560
+ memoryState: binding?.memoryState ?? carriedMemory,
1546
1561
  memoryPending: binding?.memoryPending,
1547
1562
  deviceHost: root.deviceHost,
1548
1563
  runnerId: target.runner?.id,
@@ -1841,7 +1856,7 @@ export class WorkspaceRuntime extends WorkspaceScheduler {
1841
1856
  }
1842
1857
  catch { /* keep original query */ }
1843
1858
  }
1844
- const recalled = this.knowledge.nativePeerMode || ['unsupported', 'temporary'].includes(memory.version) || !memorySettings.recall ? '' : await this.knowledge.recall(owner, agent.id, root.projectId, recallText, memory).catch(error => { this.store.event(owner, 'memory.recall.failed', { agentId: agent.id, runId: root.id, code: error instanceof HttpError ? error.code : 'memory_recall_failed', at: Date.now() }); return '记忆召回暂时失败;不能据此判断没有历史事实,可重试记忆工具。'; });
1859
+ const recalled = this.knowledge.nativePeerMode || ['unsupported', 'temporary'].includes(memory.version) || !memorySettings.recall ? '' : await this.knowledge.recall(owner, agent.id, root.projectId, recallText, memory, memoryAllowance && { self: memoryAllowance.self.allowed, shared: memoryAllowance.shared.allowed, project: memoryAllowance.project.allowed }).catch(error => { this.store.event(owner, 'memory.recall.failed', { agentId: agent.id, runId: root.id, code: error instanceof HttpError ? error.code : 'memory_recall_failed', at: Date.now() }); return '记忆召回暂时失败;不能据此判断没有历史事实,可重试记忆工具。'; });
1845
1860
  if (environment.tools.plugins && this.mcpPrepare) {
1846
1861
  const skipped = await this.mcpPrepare(owner, agent).catch(() => [{ service: 'MCP 服务', code: 'mcp_unavailable', message: 'MCP 配置读取失败,本轮未连接' }]);
1847
1862
  if (skipped.length) {
@@ -1863,6 +1878,8 @@ export class WorkspaceRuntime extends WorkspaceScheduler {
1863
1878
  .filter(message => message.role === 'user' && message.conversationId === c.id && message.conversationTaskId === run.conversationTaskId && message.steeredInto === run.runId)
1864
1879
  .sort((a, b) => a.seq - b.seq);
1865
1880
  const currentText = insertions.length ? `${text}\n\n[以下是用户在本轮准备期间按发送顺序追加的消息,请一并处理;有更正时以后面的要求为准]\n${insertions.map(message => message.content).join('\n\n')}` : text;
1881
+ // Persisted only after the prompt is admitted, like the previous baseline.
1882
+ const nextMemory = advanceMemory(memory, binding.memoryState);
1866
1883
  const promptInput = {
1867
1884
  style: dialogue.style,
1868
1885
  permissionMode: dialogue.style || dialogue.context ? (this.botControl.permissions.enabled ? (this.botControl.permissions.settings(owner).fullAccess ? 'allow' : 'ask') : agent.approvalPolicy ?? 'ask') : undefined,
@@ -1874,7 +1891,7 @@ export class WorkspaceRuntime extends WorkspaceScheduler {
1874
1891
  teamRules: team ? TEAM_TOOL_RULES : undefined,
1875
1892
  routineRules: routines ? ROUTINE_TOOL_RULES : undefined,
1876
1893
  knowledgeRules: knowledge ? BOT_KNOWLEDGE_RULES : undefined,
1877
- memory: [memory.text, recalled].filter(Boolean).join('\n\n'), noReply: NO_REPLY,
1894
+ memory: [memory.text, renderMemoryCorrections(memory, nextMemory), recalled].filter(Boolean).join('\n\n'), noReply: NO_REPLY,
1878
1895
  marker: `[yaovi-run:${run.runId}:${resultMessage.id}]`,
1879
1896
  content: currentText + (systemTurn?.description.length ? '\n[本轮系统服务目录,仅含元数据]\n' + JSON.stringify(systemTurn.description) : ''), contentKind: c.kind === 'group' || !!resetReason || !!run.sessionScopeId || externalReplies ? 'history' : 'user', attachmentRefs,
1880
1897
  };
@@ -1885,6 +1902,11 @@ export class WorkspaceRuntime extends WorkspaceScheduler {
1885
1902
  }) : undefined;
1886
1903
  if (dialogue.context && target.model && !workspacePrompt)
1887
1904
  this.store.event(owner, 'dialogue.context.fallback', { runId: root.id, reason: 'context_budget_fallback' }, c.id);
1905
+ // Character counts of what this turn sends; provider usage is reported separately.
1906
+ // Audit what memory rode along: scopes, record IDs and counts, never content.
1907
+ const memoryScopes = Object.fromEntries(['self', 'shared', 'project'].map(name => [name, memory.scopes?.includes({ self: 'agent', shared: 'user', project: 'project' }[name]) ? 'injected' : `skipped:${memorySkip ?? (memory.version === 'temporary' ? 'temporary' : name === 'project' && !root.projectId ? 'no_project' : name === 'self' && this.knowledge.nativePeerMode ? 'native_peer' : memoryAllowance?.[name].reason ?? 'not_allowed')}`]));
1908
+ this.inspector?.record(owner, c.id, { method: 'bot.memory', direction: 'request', agentId: agent.id, runId: run.runId, taskId: run.conversationTaskId, data: { scopes: memoryScopes, factIds: memory.facts.map(fact => fact.id), recalledIds: [...recalled.matchAll(/; id:([^\]\s]+)\]/g)].map(match => match[1]), corrections: nextMemory.corrections?.length ?? 0 } });
1909
+ this.store.event(owner, 'runtime.event', { botId: agent.id, runId: run.runId, workId: run.id, name: 'context.turn', value: { layered: !!workspacePrompt, contentChars: currentText.length, turnContextChars: workspacePrompt ? workspacePrompt.turnContext.length : submittedText.length, memoryChars: promptInput.memory.length, memoryScopes, correctionCount: nextMemory.corrections?.length ?? 0, estimate: true } }, c.id);
1888
1910
  this.inspector?.record(owner, c.id, { method: 'bot.environment', direction: 'request', agentId: agent.id, runId: run.runId, taskId: run.conversationTaskId,
1889
1911
  data: { snapshot: environment, computerToolIds: environmentCatalog.map(tool => tool.id), pluginWarnings: resultMessage.serviceWarnings } });
1890
1912
  binding.memoryPending = true;
@@ -1915,7 +1937,7 @@ export class WorkspaceRuntime extends WorkspaceScheduler {
1915
1937
  // Only an admitted prompt may advance the durable injection baseline.
1916
1938
  // A rejected/lost receipt must not make the next turn omit new facts.
1917
1939
  binding.memoryVersion = memory.version;
1918
- binding.memoryState = acknowledgeMemory(memory, binding.memoryState);
1940
+ binding.memoryState = nextMemory;
1919
1941
  binding.memoryPending = undefined;
1920
1942
  binding.contextSeq = Math.max(binding.contextSeq ?? 0, dispatch.contextThroughSeq);
1921
1943
  this.store.put(owner, 'binding', key, binding);
@@ -2,14 +2,19 @@ export const deviceCapabilityLabels = {
2
2
  ready: '就绪', offline: '离线', disabled: '未开放', unsupported: '不支持', not_authorized: '未授权',
3
3
  system_permission_required: '系统权限未就绪', human_control: '等待人工交还', paused: '接管中断,需重新接管并交还', agent_unavailable: '当前 Bot 不可用',
4
4
  };
5
+ export const CURRENT_DEVICE_ALIASES = ['本机', '这台设备', '这台电脑'];
6
+ export function isCurrentDeviceAlias(value) {
7
+ return CURRENT_DEVICE_ALIASES.some(alias => alias === value.trim());
8
+ }
5
9
  export function deviceSourceText(snapshot) {
6
10
  if (!snapshot.sourceHost)
7
- return '本轮消息来自未绑定控制主机的客户端;「本机」无法确定,必须指定目标电脑。历史消息的设备来源不适用于本轮。';
11
+ return '本轮未收到客户端设备身份,「本机」无法确定;「这台设备」「这台电脑」同样无法识别。请明确说明来源身份缺失,提示在当前电脑的桌面客户端检查服务器连接、重新登录或更新客户端后重试;不要列出其他电脑让用户反复选择。历史消息的设备来源不适用于本轮。';
8
12
  const host = snapshot.hosts.find(host => host.id === snapshot.sourceHost);
9
13
  const name = host?.name || snapshot.sourceHost;
14
+ const aliases = '「这台设备」「这台电脑」与「本机」同义,均指本轮消息来源电脑;身份已确定时不要再次要求用户选择设备。';
10
15
  if (!host?.online || !host.open)
11
- return `本轮用户消息来源设备:${JSON.stringify(name)}(${!host?.online ? '当前离线或未连接' : '全局未开放'})。「本机」仍指这台电脑,不能回退到服务器或其他电脑。`;
12
- return `本轮用户消息来自${host.kind === 'server' ? '服务器' : '电脑'}「${name}」。对本轮而言,「本机」指这台电脑(host=${JSON.stringify(host.target)})。`;
16
+ return `本轮用户消息来源设备:${JSON.stringify(name)}(${!host?.online ? '当前离线或未连接' : '全局未开放'})。「本机」仍指这台电脑,不能回退到服务器或其他电脑。${aliases}直接说明当前不可用的原因。`;
17
+ return `本轮用户消息来自${host.kind === 'server' ? '服务器' : '电脑'}「${name}」。对本轮而言,「本机」指这台电脑(host=${JSON.stringify(host.target)})。${aliases}`;
13
18
  }
14
19
  export function deviceInventoryText(snapshot) {
15
20
  if (!snapshot.hosts.length)
@@ -19,7 +24,8 @@ export function deviceInventoryText(snapshot) {
19
24
  const details = m ? [
20
25
  m.osRelease && `系统版本=${JSON.stringify(m.osRelease)}`, m.arch && `架构=${JSON.stringify(m.arch)}`,
21
26
  m.shell && `Shell=${JSON.stringify(m.shell)}`, m.homeDirectory && `主目录=${JSON.stringify(m.homeDirectory)}`,
22
- m.defaultCwd ? `默认工作目录=${JSON.stringify(m.defaultCwd)}` : '运行路径未上报或无文件与命令授权', m.fileRoots && `文件工具允许根目录=${JSON.stringify(m.fileRoots)}`,
27
+ m.defaultCwd ? `默认工作目录=${JSON.stringify(m.defaultCwd)}` : '运行路径未上报或无文件与命令授权',
28
+ m.fileScope === 'all' ? '文件工具范围=当前系统用户可访问的所有目录(包括其他磁盘和已连接的共享目录,受系统权限限制)' : m.fileRoots && `文件工具允许根目录=${JSON.stringify(m.fileRoots)}`,
23
29
  m.shellScope && 'Shell范围=桌面用户的系统权限(不受文件工具根目录约束)', m.timezone && `时区=${JSON.stringify(m.timezone)}`,
24
30
  ].filter(Boolean).join(';') : '运行路径未上报或无文件与命令授权';
25
31
  const state = !host.online ? '离线' : !host.open ? '未开放' : '在线';
@@ -1,7 +1,7 @@
1
1
  import { open, realpath, mkdir, mkdtemp, unlink, rename, link, rm } from 'node:fs/promises'
2
2
  import { constants } from 'node:fs'
3
3
  import { resolve, dirname, join } from 'node:path'
4
- import { hostPathInput, pathInside } from './hostPaths.mjs'
4
+ import { hostPathInput, pathInside, hostFileError } from './hostPaths.mjs'
5
5
  import { tmpdir } from 'node:os'
6
6
  import { createHash } from 'node:crypto'
7
7
 
@@ -11,13 +11,13 @@ const require = (value, message) => { if (!value) throw new Error(message) }
11
11
 
12
12
  /** Bounded, replay-safe file operations shared by native desktops and runner hosts. */
13
13
  export class FileTransferFiles {
14
- constructor(root, { confined = true } = {}) {
15
- this.root = root; this.confined = confined; this.transfers = new Map(); this.lanes = new Map(); this.closed = false
14
+ constructor(root, { confined = true, allowUNC = false } = {}) {
15
+ this.root = root; this.confined = confined; this.allowUNC = allowUNC; this.transfers = new Map(); this.lanes = new Map(); this.closed = false
16
16
  this.timer = setInterval(() => { void this.expire() }, 60000); this.timer.unref()
17
17
  }
18
18
  async path(input, writing = false) {
19
19
  require(typeof input === 'string' && input.length > 0 && input.length <= 4096 && !input.includes('\0'), '文件路径无效')
20
- const root = await realpath(this.root), raw = resolve(root, hostPathInput(input))
20
+ const root = await realpath(this.root), raw = resolve(root, hostPathInput(input, process.platform, { allowUNC: this.allowUNC }))
21
21
  const inside = value => !this.confined || pathInside(root, value)
22
22
  require(inside(raw), '路径超出允许范围')
23
23
  if (!writing) { const file = await realpath(raw); require(inside(file), '路径超出允许范围'); return file }
@@ -36,7 +36,7 @@ export class FileTransferFiles {
36
36
  const id = action.transferId, previous = this.lanes.get(id) ?? Promise.resolve()
37
37
  const next = previous.catch(() => {}).then(() => this.perform(action, authorize))
38
38
  this.lanes.set(id, next)
39
- try { return await next } finally { if (this.lanes.get(id) === next) this.lanes.delete(id) }
39
+ try { return await next } catch (error) { throw hostFileError(error) } finally { if (this.lanes.get(id) === next) this.lanes.delete(id) }
40
40
  }
41
41
  async perform(action, authorize) {
42
42
  authorize(); require(!this.closed, '传输通道已关闭')
@@ -1,12 +1,16 @@
1
1
  import path from 'node:path'
2
2
 
3
- export function hostPathInput(input, platform = process.platform) {
3
+ export function hostPathInput(input, platform = process.platform, { allowUNC = false } = {}) {
4
4
  const value = String(input ?? '')
5
5
  if (value.includes('\0')) throw new Error('路径无效')
6
6
  if (platform === 'win32') {
7
- // File tools never accept device namespaces, UNC shares, drive-relative
8
- // paths, alternate data streams, or Win32 names with ambiguous semantics.
9
- if (/^[\\/]{2}|^[A-Za-z]:(?![\\/])/.test(value)) throw new Error('路径无效')
7
+ // Only explicitly authorized desktop calls accept ordinary UNC shares.
8
+ // Device namespaces, drive-relative paths and alternate streams stay invalid.
9
+ if (/^[\\/]{2}/.test(value)) {
10
+ const [server, share] = value.slice(2).split(/[\\/]/)
11
+ if (!allowUNC || !server || !share || [server, share].some(part => ['.', '..', '?'].includes(part))) throw new Error('路径无效')
12
+ }
13
+ if (/^[A-Za-z]:(?![\\/])/.test(value) || /[<>"|?*]/.test(value)) throw new Error('路径无效')
10
14
  const rest = value.replace(/^[A-Za-z]:/, '')
11
15
  if (rest.includes(':') || rest.split(/[\\/]/).some(part =>
12
16
  (part !== '.' && part !== '..' && /[ .]$/.test(part)) || /^(con|prn|aux|nul|com[1-9]|lpt[1-9])(?:\.|$)/i.test(part))) throw new Error('路径无效')
@@ -15,6 +19,12 @@ export function hostPathInput(input, platform = process.platform) {
15
19
  return value.replace(/^~\//, '').replace(/^~$/, '')
16
20
  }
17
21
 
22
+ /** Keep OS failures distinct from application path confinement. */
23
+ export function hostFileError(error) {
24
+ const label = { EACCES: '系统权限拒绝访问', EPERM: '系统权限拒绝访问', EROFS: '文件系统只读', ENOENT: '文件或目录不存在', ENOTDIR: '路径不是目录' }[error?.code]
25
+ return label ? Object.assign(new Error(`${label}(${error.code})`, { cause: error }), { code: error.code }) : error
26
+ }
27
+
18
28
  export function pathInside(root, target, platform = process.platform) {
19
29
  const api = platform === 'win32' ? path.win32 : path.posix
20
30
  const relative = api.relative(root, target)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lsamien/yaovi",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "license": "Apache-2.0",
5
5
  "description": "Yaovi:单用户、多 Bot、多设备服务器",
6
6
  "type": "module",
@@ -43,9 +43,10 @@
43
43
  "desktop:pack:win": "npm run desktop:build:win && node scripts/windows-desktop-release.mjs",
44
44
  "desktop:test:win": "node scripts/test-windows-desktop.mjs",
45
45
  "agent:setup": "node scripts/agent-setup.mjs",
46
- "agent:test": ".venv/bin/python tests/runtime/test_kernel.py && .venv/bin/python tests/runtime/test_compression.py && .venv/bin/python tests/runtime/test_backup.py && .venv/bin/python tests/runtime/test_hermes_web.py && .venv/bin/python tests/runtime/test_native_openviking.py && .venv/bin/python tests/runtime/test_native_openviking_config.py && .venv/bin/python tests/runtime/test_vault_env.py && vitest run tests/server/embeddedAgent.test.ts tests/server/singleUserDevices.test.ts tests/server/harnessWeb.test.ts",
46
+ "agent:test": ".venv/bin/python tests/runtime/test_kernel.py && .venv/bin/python tests/runtime/test_compression.py && (cd tests/runtime && ../../.venv/bin/python test_turn_context.py) && .venv/bin/python tests/runtime/test_backup.py && .venv/bin/python tests/runtime/test_hermes_web.py && .venv/bin/python tests/runtime/test_native_openviking.py && .venv/bin/python tests/runtime/test_native_openviking_config.py && .venv/bin/python tests/runtime/test_vault_env.py && vitest run tests/server/embeddedAgent.test.ts tests/server/singleUserDevices.test.ts tests/server/harnessWeb.test.ts",
47
47
  "agent:pack": "npm run build && node scripts/build-agent-host.mjs",
48
- "postinstall": "node scripts/agent-setup.mjs --postinstall"
48
+ "postinstall": "node scripts/agent-setup.mjs --postinstall",
49
+ "desktop:release:client": "npm run desktop:build:client && node scripts/client-release.mjs"
49
50
  },
50
51
  "dependencies": {
51
52
  "@koa/bodyparser": "^5.0.0",
package/release.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "releaseVersion": "0.1.0",
4
- "webVersion": "0.1.0",
5
- "gitTag": "v0.1.0"
3
+ "releaseVersion": "0.1.2",
4
+ "webVersion": "0.1.2",
5
+ "gitTag": "v0.1.2"
6
6
  }
@@ -1125,6 +1125,23 @@ class SessionMessagesMixin:
1125
1125
  "AND role = 'user' AND active = 1 AND content IS ?",
1126
1126
  (_scrub_surrogates(api_content), row_id, session_id, self._encode_content(content)))
1127
1127
 
1128
+ def clear_user_api_content_with_prefix(self, session_id: str, prefix: str) -> int:
1129
+ """Drop user-row ``api_content`` sidecars that begin with ``prefix`` across the resume lineage.
1130
+
1131
+ Yaovi sends its per-turn application state only with the live request; later turns must
1132
+ replay the clean ``content`` rather than that state. Sidecars without the prefix
1133
+ (cache-fidelity stamps, sanitize divergences) and rows written before the prefix existed
1134
+ are left untouched.
1135
+ """
1136
+ ids = [sid for sid in dict.fromkeys(self._resume_lineage_ids(session_id)) if sid]
1137
+ if not ids or not prefix:
1138
+ return 0
1139
+ marks = ",".join("?" * len(ids))
1140
+ return self._write_rowcount(
1141
+ f"UPDATE messages SET api_content = NULL WHERE session_id IN ({marks}) AND role = 'user' "
1142
+ "AND api_content IS NOT NULL AND substr(api_content, 1, ?) = ?",
1143
+ (*ids, len(prefix), prefix))
1144
+
1128
1145
  def set_user_message_content(self, session_id: str, row_id: int, content: Any) -> int:
1129
1146
  """Rewrite the content of ONE known active user row. Used when a user turn was written at submit
1130
1147
  time (before the agent ran) and the turn prologue then rewrote the prompt it persists (@-file
package/runtime/worker.py CHANGED
@@ -22,6 +22,8 @@ steer_lock = threading.Lock()
22
22
  turn_closed = False
23
23
  steer_ready = False
24
24
  startup_steers = []
25
+ # Prefix of the per-turn application state Yaovi renders (src/server/workspacePrompt.ts).
26
+ TURN_CONTEXT_SENTINEL = "[[YAOVI_TURN_CONTEXT_V1]]"
25
27
 
26
28
 
27
29
  def emit(kind, **payload):
@@ -106,6 +108,26 @@ def read_replies():
106
108
  interrupt()
107
109
 
108
110
 
111
+ def strip_turn_context_sidecars(history):
112
+ """Earlier turns replay their user content only, never the per-turn state sent with them."""
113
+ for message in history:
114
+ sidecar = message.get("api_content")
115
+ if message.get("role") == "user" and isinstance(sidecar, str) and sidecar.startswith(TURN_CONTEXT_SENTINEL):
116
+ message.pop("api_content", None)
117
+ return history
118
+
119
+
120
+ def compose_turn(text, turn_context):
121
+ """``(model-facing message, persisted message)``; state reaches this turn's calls only."""
122
+ if not turn_context:
123
+ return text, None
124
+ if not str(turn_context).startswith(TURN_CONTEXT_SENTINEL):
125
+ raise ValueError("Turn context is missing its sentinel")
126
+ if isinstance(text, list):
127
+ return [{"type": "text", "text": turn_context}, *text], text
128
+ return turn_context + "\n\n" + str(text), text
129
+
130
+
109
131
  def clarify(questions):
110
132
  answers = {}
111
133
  for question in questions:
@@ -224,6 +246,10 @@ def main():
224
246
  pending_calls -= {m.get("tool_call_id") for m in history if m.get("role") == "tool"}
225
247
  for call in pending_calls:
226
248
  history.append({"role":"tool", "tool_call_id":call, "content":json.dumps({"error":"Interrupted before the result was confirmed. Inspect effects before retrying.","outcomeUnknown":True})})
249
+ # A turn that crashed before its cleanup must not leak its state into this one,
250
+ # including rows reloaded from the store after in-turn compaction.
251
+ db.clear_user_api_content_with_prefix(session, TURN_CONTEXT_SENTINEL)
252
+ strip_turn_context_sidecars(history)
227
253
  # Existing sessions retain their admitted tools and context. Only the known
228
254
  # application identity prefix changes, at a turn boundary before model use.
229
255
  row = db.get_session(session)
@@ -287,7 +313,14 @@ def main():
287
313
  if not result:
288
314
  raise RuntimeError("Native skill review did not finish")
289
315
  else:
290
- result = agent.run_conversation(request["text"], conversation_history=history)
316
+ message, persisted = compose_turn(request["text"], request.get("turnContext"))
317
+ try:
318
+ result = agent.run_conversation(message, conversation_history=history,
319
+ **({"persist_user_message": persisted} if persisted is not None else {}))
320
+ finally:
321
+ if persisted is not None:
322
+ for lineage in dict.fromkeys((session, agent.session_id)):
323
+ db.clear_user_api_content_with_prefix(lineage, TURN_CONTEXT_SENTINEL)
291
324
  with steer_lock:
292
325
  turn_closed = True
293
326
  with agent._pending_redirect_lock:
@@ -1 +0,0 @@
1
- {"version":3,"file":"AboutDialog-BW25cB-W.js","names":[],"sources":["../../package.json","../../src/client/components/app/AboutDialog.vue","../../src/client/components/app/AboutDialog.vue"],"sourcesContent":["{\n \"name\": \"@lsamien/yaovi\",\n \"version\": \"0.1.0\",\n \"license\": \"Apache-2.0\",\n \"description\": \"Yaovi:单用户、多 Bot、多设备服务器\",\n \"type\": \"module\",\n \"main\": \"desktop/main.mjs\",\n \"engines\": {\n \"node\": \">=24.0.0\"\n },\n \"bin\": {\n \"yaovi\": \"bin/yaovi-agent.mjs\",\n \"yaovi-agent\": \"bin/yaovi-agent.mjs\"\n },\n \"scripts\": {\n \"dev\": \"cross-env NODE_ENV=development tsx watch src/server/index.ts\",\n \"build\": \"npm run brand:verify && npm run release:verify && vue-tsc -b tsconfig.client.build.json && vite build && node scripts/clean-server.mjs && tsc -p tsconfig.server.json && node scripts/copy-worker.mjs && node scripts/write-build-info.mjs\",\n \"start\": \"node bin/yaovi-agent.mjs start\",\n \"typecheck\": \"vue-tsc -b && tsc --noEmit -p tsconfig.server.json\",\n \"test\": \"vitest run\",\n \"prepack\": \"npm run build\",\n \"test:watch\": \"vitest\",\n \"test:e2e\": \"npm run build && playwright test\",\n \"test:live\": \"cross-env YAOVI_LIVE_TEST=1 vitest run tests/live\",\n \"brand:verify\": \"node scripts/verify-brand.mjs\",\n \"release:verify\": \"node scripts/verify-release.mjs\",\n \"verify:lifecycle\": \"node scripts/verify-lifecycle.mjs\",\n \"test:e2e:workspace\": \"npm run build && playwright test -c playwright.workspace.config.ts\",\n \"test:e2e:desktop-hosts\": \"npm run build && playwright test -c playwright.desktop-hosts.config.ts\",\n \"test:e2e:team-tools\": \"npm run build && playwright test -c playwright.team-tools.config.ts\",\n \"desktop:build\": \"npm run build && node scripts/build-desktop.mjs\",\n \"desktop:build:client\": \"node scripts/write-build-info.mjs && node scripts/build-desktop-client.mjs\",\n \"desktop:dev:client\": \"npm run desktop:build:client && cross-env YAOVI_DESKTOP_CLIENT_ONLY=1 electron .\",\n \"desktop:pack:client\": \"npm run desktop:build:client && electron-builder --config electron-builder.client.yml --mac --arm64 --publish never\",\n \"desktop:dev\": \"npm run desktop:build && cross-env YAOVI_DESKTOP_CLIENT_ONLY=0 electron .\",\n \"desktop:pack\": \"npm run desktop:build && electron-builder --mac --arm64 --publish never\",\n \"desktop:release\": \"npm run desktop:build && node scripts/desktop-release.mjs\",\n \"desktop:test\": \"cross-env YAOVI_DESKTOP_CLIENT_ONLY=0 node --test desktop/*.test.mjs\",\n \"runner\": \"node dist-server/runner/index.js\",\n \"runner:dev\": \"tsx src/runner/index.ts\",\n \"runner:build\": \"node scripts/build-runner.mjs\",\n \"desktop:build:win\": \"npm run build && node scripts/build-windows-desktop.mjs\",\n \"desktop:pack:win\": \"npm run desktop:build:win && node scripts/windows-desktop-release.mjs\",\n \"desktop:test:win\": \"node scripts/test-windows-desktop.mjs\",\n \"agent:setup\": \"node scripts/agent-setup.mjs\",\n \"agent:test\": \".venv/bin/python tests/runtime/test_kernel.py && .venv/bin/python tests/runtime/test_compression.py && .venv/bin/python tests/runtime/test_backup.py && .venv/bin/python tests/runtime/test_hermes_web.py && .venv/bin/python tests/runtime/test_native_openviking.py && .venv/bin/python tests/runtime/test_native_openviking_config.py && .venv/bin/python tests/runtime/test_vault_env.py && vitest run tests/server/embeddedAgent.test.ts tests/server/singleUserDevices.test.ts tests/server/harnessWeb.test.ts\",\n \"agent:pack\": \"npm run build && node scripts/build-agent-host.mjs\",\n \"postinstall\": \"node scripts/agent-setup.mjs --postinstall\"\n },\n \"dependencies\": {\n \"@koa/bodyparser\": \"^5.0.0\",\n \"@koa/router\": \"^15.4.0\",\n \"@openviking/sdk\": \"0.1.0\",\n \"@trycua/cua-driver\": \"0.28.2\",\n \"@types/busboy\": \"^1.5.4\",\n \"@types/koa\": \"^2.15.0\",\n \"@types/koa-send\": \"^4.1.6\",\n \"@types/koa-static\": \"^4.0.4\",\n \"@types/markdown-it\": \"^14.1.2\",\n \"@types/mime-types\": \"^2.1.4\",\n \"@types/node\": \"^24.10.0\",\n \"@types/qrcode\": \"^1.5.6\",\n \"@types/ws\": \"^8.18.1\",\n \"@vicons/ionicons5\": \"^0.13.0\",\n \"@vicons/tabler\": \"^0.13.0\",\n \"@vitejs/plugin-vue\": \"^6.0.5\",\n \"@vue/tsconfig\": \"^0.9.1\",\n \"busboy\": \"^1.6.0\",\n \"cookie\": \"^1.0.2\",\n \"docx-preview\": \"^0.3.7\",\n \"dompurify\": \"^3.2.6\",\n \"highlight.js\": \"^11.11.1\",\n \"koa\": \"^2.15.3\",\n \"koa-send\": \"^5.0.1\",\n \"koa-static\": \"^5.0.0\",\n \"libsodium-wrappers-sumo\": \"0.8.4\",\n \"markdown-it\": \"^14.1.0\",\n \"mime-types\": \"^3.0.1\",\n \"naive-ui\": \"^2.44.1\",\n \"pdfjs-dist\": \"6.2.108\",\n \"pinia\": \"^3.0.4\",\n \"pinyin-pro\": \"3.29.5\",\n \"playwright\": \"1.62.1\",\n \"qrcode\": \"^1.5.4\",\n \"read-excel-file\": \"^7.0.3\",\n \"sass\": \"^1.99.0\",\n \"ssh2\": \"1.17.0\",\n \"typescript\": \"~6.0.2\",\n \"undici\": \"7.29.1\",\n \"vite\": \"^8.0.4\",\n \"vue\": \"^3.5.32\",\n \"vue-router\": \"^4.6.4\",\n \"vue-tsc\": \"^3.2.8\",\n \"ws\": \"^8.20.0\",\n \"zod\": \"^4.1.12\"\n },\n \"devDependencies\": {\n \"@playwright/test\": \"^1.60.0\",\n \"@types/jsdom\": \"^21.1.7\",\n \"@types/ssh2\": \"1.15.5\",\n \"@types/supertest\": \"^6.0.3\",\n \"@vitest/coverage-v8\": \"^3.2.4\",\n \"@vue/test-utils\": \"^2.4.6\",\n \"cross-env\": \"^10.1.0\",\n \"electron\": \"43.4.0\",\n \"electron-builder\": \"26.15.3\",\n \"electron-updater\": \"6.8.9\",\n \"esbuild\": \"0.28.2\",\n \"js-yaml\": \"4.3.2\",\n \"jsdom\": \"^27.0.1\",\n \"supertest\": \"^7.1.4\",\n \"tsx\": \"^4.21.0\",\n \"vitest\": \"^3.2.4\"\n },\n \"files\": [\n \"bin\",\n \"dist\",\n \"dist-server\",\n \"licenses\",\n \"LICENSE\",\n \"NOTICE\",\n \"THIRD_PARTY_NOTICES.md\",\n \"release.json\",\n \"build-info.json\",\n \"runtime\",\n \"scripts/agent-backup.py\",\n \"scripts/agent-setup.mjs\",\n \"!runtime/**/__pycache__/**\",\n \"!runtime/**/*.pyc\",\n \"!runtime/**/*.pyo\",\n \"!runtime/**/.env\",\n \"!runtime/**/auth.json\"\n ],\n \"publishConfig\": {\n \"access\": \"public\"\n }\n}\n","<script setup lang=\"ts\">\nimport { version } from '../../../../package.json'\nimport BrandMark from '@/components/common/BrandMark.vue'\nimport StandaloneDialog from '@/components/common/StandaloneDialog.vue'\nconst emit = defineEmits<{ close: [] }>()\n</script>\n<template>\n <StandaloneDialog title=\"关于Yaovi\" compact @close=\"emit('close')\"><section class=\"about-content\">\n <BrandMark :size=\"56\" :label=\"false\" compact bare /><h3>Yaovi</h3><p class=\"about-version\">版本 {{ version }}</p>\n <p>通过 OpenAI 模型与 Bot 对话、协作完成任务。<br />基于 Apache-2.0 许可证开放源代码。</p>\n\n </section></StandaloneDialog>\n</template>\n<style scoped>\n.about-content{display:grid;justify-items:center;gap:12px;text-align:center}.about-content h3{font-size:21px;margin:0}.about-content p{margin:0;color:var(--text-secondary);font-size:13px;line-height:1.8}.about-content .about-version{font-size:12px}.about-content nav{display:flex;flex-wrap:wrap;justify-content:center;gap:8px;margin-top:8px}.about-content a{padding:10px 8px;color:var(--accent);font-size:13px;text-decoration:none}.about-content a:hover{text-decoration:underline}.about-content a:focus-visible{outline:2px solid var(--accent);outline-offset:2px}\n</style>\n","<script setup lang=\"ts\">\nimport { version } from '../../../../package.json'\nimport BrandMark from '@/components/common/BrandMark.vue'\nimport StandaloneDialog from '@/components/common/StandaloneDialog.vue'\nconst emit = defineEmits<{ close: [] }>()\n</script>\n<template>\n <StandaloneDialog title=\"关于Yaovi\" compact @close=\"emit('close')\"><section class=\"about-content\">\n <BrandMark :size=\"56\" :label=\"false\" compact bare /><h3>Yaovi</h3><p class=\"about-version\">版本 {{ version }}</p>\n <p>通过 OpenAI 模型与 Bot 对话、协作完成任务。<br />基于 Apache-2.0 许可证开放源代码。</p>\n\n </section></StandaloneDialog>\n</template>\n<style scoped>\n.about-content{display:grid;justify-items:center;gap:12px;text-align:center}.about-content h3{font-size:21px;margin:0}.about-content p{margin:0;color:var(--text-secondary);font-size:13px;line-height:1.8}.about-content .about-version{font-size:12px}.about-content nav{display:flex;flex-wrap:wrap;justify-content:center;gap:8px;margin-top:8px}.about-content a{padding:10px 8px;color:var(--accent);font-size:13px;text-decoration:none}.about-content a:hover{text-decoration:underline}.about-content a:focus-visible{outline:2px solid var(--accent);outline-offset:2px}\n</style>\n"],"mappings":"wVCIA,IAAM,EAAO,gBAGX,EAAA,EAAA,EAI6B,EAAA,CAJX,MAAM,UAAU,QAAA,GAAS,QAAK,AAAA,EAAA,KAAA,GAAE,EAAI,OAAA,IAAW,QAAA,MAIvD,CAJuD,EAIvD,UAJuD,EAIvD,CAHR,EAAoD,EAAA,CAAxC,KAAM,GAAK,MAAO,GAAO,QAAA,GAAQ,KAAA,KAAO,AAAA,EAAA,KAAA,EAAc,KAAA,KAAV,QAAK,EAAA,EAAK,EAA6C,IAA7C,EAAyB,MAAG,EAAG,EAAA,CAAA,CAAO,EAAA,CAAA,EACxG,AAAA,EAAA,KAAA,EAAgE,IAAA,KAAA,CAA7D,EAAA,8BAA4B,EAAA,EAAM,IAAA,EAAA,EAAA,yBAAuB"}