@arcfold/dsh-method-ui 0.0.0-stage → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +2 -2
  3. package/THIRD_PARTY_NOTICES.md +11 -0
  4. package/lib/client.js +4237 -0
  5. package/package.json +35 -4
  6. package/src/Brand.mjs +17 -0
  7. package/src/CollaborationView.mjs +56 -0
  8. package/src/DesktopModels.mjs +40 -0
  9. package/src/DesktopSetup.mjs +48 -0
  10. package/src/GoalDock.mjs +130 -0
  11. package/src/GoalWorkList.mjs +52 -0
  12. package/src/ImportDock.mjs +172 -0
  13. package/src/MethodDock.mjs +145 -0
  14. package/src/ModelSettings.mjs +144 -0
  15. package/src/OpportunityDock.mjs +66 -0
  16. package/src/UnderstandingDock.mjs +117 -0
  17. package/src/browse-scope.mjs +6 -0
  18. package/src/browse-source.mjs +31 -0
  19. package/src/client.mjs +138 -0
  20. package/src/desktop-setup-host.mjs +30 -0
  21. package/src/desktop-workspace.mjs +8 -0
  22. package/src/goal-host.mjs +133 -0
  23. package/src/goal-locales.mjs +78 -0
  24. package/src/goal-source.mjs +72 -0
  25. package/src/import-host.mjs +98 -0
  26. package/src/import-locales.mjs +126 -0
  27. package/src/import-source.mjs +83 -0
  28. package/src/index.mjs +127 -0
  29. package/src/locales.mjs +153 -0
  30. package/src/main-session.mjs +7 -0
  31. package/src/method-source.mjs +82 -0
  32. package/src/model-host.mjs +56 -0
  33. package/src/model-settings-locales.mjs +91 -0
  34. package/src/model-settings-source.mjs +119 -0
  35. package/src/opportunity-host.mjs +118 -0
  36. package/src/opportunity-locales.mjs +34 -0
  37. package/src/opportunity-source.mjs +56 -0
  38. package/src/style.css +99 -0
  39. package/src/tree-client.mjs +15 -0
  40. package/src/tree-host.mjs +39 -0
  41. package/src/tree-source.mjs +65 -0
  42. package/src/tree-style.css +19 -0
  43. package/src/tree-view.mjs +278 -0
  44. package/src/understanding-host.mjs +123 -0
  45. package/src/understanding-locales.mjs +38 -0
  46. package/src/understanding-source.mjs +64 -0
@@ -0,0 +1,126 @@
1
+ export const importsZh = {
2
+ importSnapshot:'以下为当时保存的导入预览,资格、来源状态和试用次数不代表当前状态。',
3
+ importWithdrawnNotice:'这批导入已撤回,不再作为当前依据。以下保留当时预览和原文。',
4
+ importCountsAtPreview:'当时预览统计', importEligibilityAtPreview:'当时预览资格',
5
+ importSourceAtPreview:'当时来源状态', importUsageAtPreview:'导入时试用次数',
6
+ importContent:'读取完整内容', importOriginalRecord:'查看保留的旧记录',
7
+ importAppliesTo:'适用情况', importDoesNotApplyTo:'不适用情况', importTopics:'原主题范围',
8
+ importAttributeUnavailable:'这项内容不完整或超出可采用范围,请查看保留的旧记录。',
9
+ importClaimInference:'AI 推断', importClaimLiteral:'用户明确陈述', importClaimQuestion:'待确认的问题',
10
+ importReason_user_authored_range_unproven:'无法确认这段引文是用户自己的陈述,而非他引用的资料。',
11
+ "importReason_old_state_not_current":"旧记录已停止使用,或无法确认它仍然有效。",
12
+ "importReason_source_closure_unproven":"无法完整核对这条记录所依据的来源。",
13
+ "importReason_source_reference_unproven":"引用的来源或版本无法核对。",
14
+ "importReason_source_range_mismatch":"引文与原文中的位置不一致。",
15
+ "importReason_old_tasks_history_only":"旧事项保留为历史,不会恢复执行。",
16
+ "importReason_source_cycle":"依据之间存在循环引用,需要核对。",
17
+ "importReason_old_record_not_current":"旧记录的状态、版本或保存时间范围无法确认。",
18
+ "importReason_source_unavailable":"所依赖的来源已撤回、缺失或不可用。",
19
+ "importReason_source_revision_unproven":"缺少原来源版本依据,不能按最新版本补齐。",
20
+ "importReason_dependency_version_unproven":"依赖的理解或方法已经变化,或其版本无法核对。",
21
+ "importReason_method_contract_unproven":"方法内容、适用条件或状态不完整。",
22
+ "importReason_method_trial_basis_unknown":"缺少原试用次数依据,不会重新按零次开始。",
23
+ "importReason_method_trial_history_mismatch":"原试用次数与采用记录不一致。",
24
+ "importReason_method_active_authority_unproven":"无法确认此前继续采用这条方法的明确决定。",
25
+ "importReason_historical_control_not_migrated":"保留目标或机会的历史,暂不迁入控制或执行状态。",
26
+ "importReason_scope_not_mapped":"这条记录只适用于旧交流,尚未关联当前会话。",
27
+ "importReason_dependency_not_eligible":"所依赖的其他记录在本批预览中不可采用。",
28
+ "importReason_method_already_present":"方法已存在,沿用当前状态和已用次数,不会重新试用。",
29
+ "importReason_method_existing_conflict":"与已有方法的内容或范围不一致,不会覆盖现有决定。",
30
+ "importReason_method_batch_conflict":"这批旧记录中有重复方法,需先核对;不会重复增加试用次数。",
31
+ "importReason_dependency_scope_mismatch":"依赖材料的适用范围更窄,不能扩大到其他交流。",
32
+ "importReason_scope_unsupported":"旧适用范围无法准确迁入,需要核对后再使用。",
33
+ "importRole_user":"用户原话",
34
+ "importRole_assistant":"助手回答",
35
+ "importRole_tool":"工具结果",
36
+ "importRole_system":"系统信息",
37
+ "importRole_material":"资料",
38
+ "importRole_program":"程序产生的信息",
39
+ "importRoleUnknown":"来源角色待核对",
40
+ "importReasonUnrecognized":"这条记录还需要进一步核对。",
41
+ "importExistingRecord":"对应已有方法记录",
42
+
43
+ importWorking:'正在处理…', importTitle:'旧历史导入', importOpen:'查看导入', importHide:'收起导入', importEmpty:'还没有导入批次。',
44
+ importBoundary:'先选择完整导出文件并查看预览。只有确认后,列出的理解与方法才会按其适用范围进入后续交流;不会恢复旧任务或授予执行权限。',
45
+ importFile:'完整导出文件(最多 768 MiB)', importPrepare:'上传并预览', importPreparing:'正在上传或核对完整文件…',
46
+ importStorage:'上传的文件会保存在本地附件中。未确认的预览不会影响回答;撤回后保留原文与操作记录。',
47
+ importLoading:'正在读取导入记录…', importPreview:'查看这一批', importRecords:'理解、方法与历史', importSources:'原始来源',
48
+ importText:'读取完整原文', importTextPage:'完整内容(分段)', importExcerpt:'以下是节选,可分段查看完整原文。',
49
+ importReady:'待确认', importPreparingStatus:'准备中', importFailed:'准备失败', importConfirmed:'已导入', importWithdrawn:'已撤回',
50
+ importScopeTitle:'旧交流的适用范围', importKeepScope:'保留原范围;旧会话内容仅作为历史',
51
+ importMapScope:'将选中的一段旧交流关联到当前会话', importScopeHelp:'全局内容仍按原全局范围处理。最多关联一段旧交流;其他旧会话保留为历史,不影响当前交流。',
52
+ importScopeChanged:'范围已修改。请先重新生成预览,再确认导入。', importReprepare:'按新范围生成预览',
53
+ importNoReceipt:'若要修改范围,请重新选择原导出文件并预览。', importMapped:'关联到当前会话', importHistoricalScope:'保留在旧会话', importGlobal:'沿用原全局范围',
54
+ importConfirm:'确认导入这一批', importConfirmHelp:'这一次确认包含本批列出的指导内容和方法试用次数。原文与推断仍有区别,导入不表示内容已经证实。',
55
+ importWithdraw:'撤回这一批的采用', importWithdrawHelp:'撤回后将停止采用依赖本批来源的理解、方法与目标依据;独立的新经历仍保留,目标的暂停或关闭状态不变,已执行的工作不会撤销。',
56
+ importOtherSession:'这是另一段会话准备的预览,请回到原会话确认。',
57
+ importUnknown:'结果尚未确认,没有自动重发。请刷新批次查看实际状态。', importSaved:'导入状态已更新。',
58
+ importEnabled:'可采用', importTrial:'有限试用', importPaused:'保持暂停', importHistory:'仅保留历史', importNeedsReview:'需要核对',
59
+ importCountSources:'来源', importCountRecords:'记录', importRemaining:'剩余试用次数', importUsed:'已用', importMaximum:'上限',
60
+ importUnavailable:'来源不可用', importAvailable:'来源可用', importRevision:'预览版本', importFileHash:'文件指纹',
61
+ importReadMore:'下一段', importReadPrevious:'上一段', importReason:'资格核对结果', importFileTooLarge:'请选择不超过 768 MiB 的非空导出文件。',
62
+ };
63
+ export const importsEn = {
64
+ importSnapshot:'This is the saved import preview. Eligibility, source availability and trial counts describe that preview, not the current state.',
65
+ importWithdrawnNotice:'This batch has been withdrawn and is no longer used as evidence. Its original preview and source text remain below.',
66
+ importCountsAtPreview:'Preview counts', importEligibilityAtPreview:'Eligibility at preview',
67
+ importSourceAtPreview:'Source state at preview', importUsageAtPreview:'Trial counts at import',
68
+ importContent:'Read full content', importOriginalRecord:'View retained earlier record',
69
+ importAppliesTo:'Applies to', importDoesNotApplyTo:'Does not apply to', importTopics:'Original topics',
70
+ importAttributeUnavailable:'This field is incomplete or outside the eligible bounds. View the retained earlier record.',
71
+ importClaimInference:'AI inference', importClaimLiteral:'Explicit user statement', importClaimQuestion:'Open question',
72
+ importReason_user_authored_range_unproven:'The quotation is not proven to be the user’s own statement rather than cited material.',
73
+ "importReason_old_state_not_current":"The earlier state is stopped or unproven.",
74
+ "importReason_source_closure_unproven":"The complete supporting evidence is unproven.",
75
+ "importReason_source_reference_unproven":"The cited source or version is unproven.",
76
+ "importReason_source_range_mismatch":"The quotation does not match its recorded position.",
77
+ "importReason_old_tasks_history_only":"Earlier tasks remain historical and do not resume execution.",
78
+ "importReason_source_cycle":"The evidence contains a circular reference.",
79
+ "importReason_old_record_not_current":"The earlier state, version or snapshot boundary is unproven.",
80
+ "importReason_source_unavailable":"A supporting source is withdrawn, missing or unavailable.",
81
+ "importReason_source_revision_unproven":"The original source version is unproven and cannot be replaced with the latest version.",
82
+ "importReason_dependency_version_unproven":"A supporting record changed or its version is unproven.",
83
+ "importReason_method_contract_unproven":"The method content, applicability or state is incomplete.",
84
+ "importReason_method_trial_basis_unknown":"Trial history is missing; usage will not restart from zero.",
85
+ "importReason_method_trial_history_mismatch":"Trial counts and adoption records disagree.",
86
+ "importReason_method_active_authority_unproven":"The earlier explicit continuation decision is unproven.",
87
+ "importReason_historical_control_not_migrated":"Goal or opportunity history is retained without restoring controls or execution.",
88
+ "importReason_scope_not_mapped":"The earlier conversation is not connected here.",
89
+ "importReason_dependency_not_eligible":"A supporting record is not eligible in this preview.",
90
+ "importReason_method_already_present":"The method already exists; its current state and usage are retained.",
91
+ "importReason_method_existing_conflict":"It conflicts with an existing method or scope and will not overwrite the current decision.",
92
+ "importReason_method_batch_conflict":"These old records contain duplicate methods; review them first. Trial uses will not be duplicated.",
93
+ "importReason_dependency_scope_mismatch":"A supporting record has a narrower scope and cannot be used in other conversations.",
94
+ "importReason_scope_unsupported":"The earlier scope cannot be transferred accurately and needs review.",
95
+ "importRole_user":"User",
96
+ "importRole_assistant":"Assistant",
97
+ "importRole_tool":"Tool result",
98
+ "importRole_system":"System",
99
+ "importRole_material":"Material",
100
+ "importRole_program":"Program-generated information",
101
+ "importRoleUnknown":"Source role unconfirmed",
102
+ "importReasonUnrecognized":"This record needs further review.",
103
+ "importExistingRecord":"View existing record reference",
104
+
105
+ importWorking:'Processing…', importTitle:'Import history', importOpen:'View imports', importHide:'Hide imports', importEmpty:'No import batches yet.',
106
+ importBoundary:'Choose a complete export and review its preview. Understanding and methods become eligible only after confirmation, within the displayed scope. Old tasks and execution permissions are not restored.',
107
+ importFile:'Complete export (up to 768 MiB)', importPrepare:'Upload and preview', importPreparing:'Uploading or checking the complete file…',
108
+ importStorage:'Uploaded files remain in local attachments. Unconfirmed previews do not affect answers. Withdrawal retains originals and audit records.',
109
+ importLoading:'Loading import records…', importPreview:'View this batch', importRecords:'Understanding, methods and history', importSources:'Original sources',
110
+ importText:'Read full original', importTextPage:'Complete content segments', importExcerpt:'This is an excerpt. The complete original is available in segments.',
111
+ importReady:'Awaiting confirmation', importPreparingStatus:'Preparing', importFailed:'Preparation failed', importConfirmed:'Imported', importWithdrawn:'Withdrawn',
112
+ importScopeTitle:'Scope of earlier conversations', importKeepScope:'Keep original scope; earlier conversations remain historical',
113
+ importMapScope:'Connect the selected earlier conversation to this conversation', importScopeHelp:'Global material retains its global scope. At most one earlier conversation can be connected; all others remain historical.',
114
+ importScopeChanged:'Scope changed. Generate a new preview before confirming.', importReprepare:'Preview the new scope',
115
+ importNoReceipt:'To change scope, select the original export file again.', importMapped:'Connected to this conversation', importHistoricalScope:'Earlier conversation only', importGlobal:'Original global scope',
116
+ importConfirm:'Confirm this batch', importConfirmHelp:'One confirmation covers the listed guidance and method trial counts. Originals and inferences remain distinct; import does not prove their correctness.',
117
+ importWithdraw:'Withdraw this batch', importWithdrawHelp:'Dependent understanding, methods and goal evidence stop being eligible. Independent new experience remains. Goal pause/close decisions and already executed work are not undone.',
118
+ importOtherSession:'This preview belongs to another conversation. Confirm it in the original conversation.',
119
+ importUnknown:'The outcome is unconfirmed. Nothing was resent automatically. Refresh to inspect the batch.', importSaved:'Import status updated.',
120
+ importEnabled:'Eligible', importTrial:'Limited trial', importPaused:'Remains paused', importHistory:'Historical only', importNeedsReview:'Needs review',
121
+ importCountSources:'Sources', importCountRecords:'Records', importRemaining:'Trials remaining', importUsed:'Used', importMaximum:'Limit',
122
+ importUnavailable:'Source unavailable', importAvailable:'Source available', importRevision:'Preview revision', importFileHash:'File fingerprint',
123
+ importReadMore:'Next segment', importReadPrevious:'Previous segment', importReason:'Eligibility checks', importFileTooLarge:'Choose a nonempty export file up to 768 MiB.',
124
+ };
125
+
126
+ export const importReasonKeys = Object.freeze(["user_authored_range_unproven", "old_state_not_current", "source_closure_unproven", "source_reference_unproven", "source_range_mismatch", "old_tasks_history_only", "source_cycle", "old_record_not_current", "source_unavailable", "source_revision_unproven", "dependency_version_unproven", "method_contract_unproven", "method_trial_basis_unknown", "method_trial_history_mismatch", "method_active_authority_unproven", "historical_control_not_migrated", "scope_not_mapped", "dependency_not_eligible", "method_already_present", "method_existing_conflict", "method_batch_conflict", "dependency_scope_mismatch", "scope_unsupported"]);
@@ -0,0 +1,83 @@
1
+ import { requireViewScope, viewScopeFields } from './browse-source.mjs';
2
+ // Matches the host acceptance limit; native ordinary uploads have their own policy.
3
+ export const IMPORT_MAX_FILE_BYTES = 805306368;
4
+ export function createImportSource({ rpc, fileUpload, sessionId, viewScope, subscribeReset, intervalMs = 5000 }) {
5
+ viewScopeFields(viewScope);
6
+ let snapshot = { loading: true, error: null, items: [], total: 0, offset: 0, nextOffset: null,
7
+ canImport: viewScope === undefined ? undefined : false, readEpoch: 0 };
8
+ let generation = 0, listRead, timer, reset, disposed = false;
9
+ const listeners = new Set(), pending = new Set();
10
+ const stopped = () => Object.assign(new Error('连接已经变化,请重新读取导入记录。'), { code: 'import_request_cancelled' });
11
+ const publish = value => { snapshot = { ...snapshot, ...value }; for (const listener of listeners) listener(); };
12
+ async function operation(fn) {
13
+ if (disposed) throw stopped();
14
+ const controller = new AbortController(), epoch = snapshot.readEpoch; pending.add(controller);
15
+ try {
16
+ const result = await fn(controller.signal);
17
+ if (disposed || controller.signal.aborted || epoch !== snapshot.readEpoch) throw stopped();
18
+ return result;
19
+ } finally { pending.delete(controller); }
20
+ }
21
+ async function call(endpoint, request, signal) {
22
+ const scope = ['list', 'details'].includes(endpoint) ? viewScopeFields(viewScope) : {};
23
+ const result = await rpc.call('/api', `arcfoldImports/${endpoint}`, { args: { request: { ...request, sessionId, ...scope } } }, signal);
24
+ if (result?.ok !== true) throw Object.assign(new Error(result?.error?.message || '导入操作未完成。'),
25
+ { code: result?.error?.code || 'import_request_failed', remote: true });
26
+ return result.value;
27
+ }
28
+ function invalidate() {
29
+ listRead?.abort(); generation++;
30
+ for (const controller of pending) controller.abort();
31
+ publish({ loading: true, error: null, items: [], total: 0, offset: 0, nextOffset: null,
32
+ canImport: viewScope === undefined ? undefined : false, readEpoch: snapshot.readEpoch + 1 });
33
+ }
34
+ async function refresh(offset = snapshot.offset) {
35
+ if (disposed) return;
36
+ listRead?.abort(); listRead = new AbortController();
37
+ const current = ++generation, signal = listRead.signal;
38
+ try {
39
+ const result = await call('list', { offset, limit: 20 }, signal);
40
+ if (!disposed && !signal.aborted && current === generation) publish({ ...result, offset, loading: false, error: null });
41
+ } catch (error) {
42
+ if (!disposed && !signal.aborted && current === generation) publish({ loading: false, error: error.message });
43
+ }
44
+ }
45
+ function stop() { clearInterval(timer); timer = undefined; reset?.(); reset = undefined; invalidate(); }
46
+ return {
47
+ getSnapshot: () => snapshot,
48
+ subscribe(listener) {
49
+ if (disposed) return () => {};
50
+ listeners.add(listener);
51
+ if (listeners.size === 1) {
52
+ void refresh(); timer = setInterval(() => { void refresh(); }, intervalMs);
53
+ reset = subscribeReset(() => { invalidate(); void refresh(0); });
54
+ }
55
+ return () => { listeners.delete(listener); if (!listeners.size) stop(); };
56
+ },
57
+ refresh,
58
+ setViewScope(value) {
59
+ requireViewScope(value);
60
+ if (disposed || viewScope === value) return;
61
+ viewScope = value; invalidate();
62
+ if (listeners.size) void refresh(0);
63
+ },
64
+ details: request => operation(signal => call('details', request, signal)),
65
+ prepare: request => operation(signal => call('prepare', request, signal)),
66
+ confirm: request => operation(signal => call('confirm', request, signal)),
67
+ withdraw: request => operation(signal => call('withdraw', request, signal)),
68
+ upload(file, onProgress) {
69
+ return operation(async signal => {
70
+ if (!(file instanceof Blob) || !Number.isSafeInteger(file.size) || file.size < 1 || file.size > IMPORT_MAX_FILE_BYTES)
71
+ throw Object.assign(new Error('请选择不超过 768 MiB 的完整导出文件。'), { code: 'invalid_import_file', remote: true });
72
+ // File/Blob preserves the native streaming path; do not read it into a byte array.
73
+ const result = await fileUpload.upload(sessionId, file, file.name || 'arcfold-export.json', signal, onProgress);
74
+ if (result?.ok !== true) throw Object.assign(new Error(result?.error?.message || '文件上传未完成。'),
75
+ { code: result?.error?.code || 'import_upload_failed', remote: true });
76
+ if (typeof result.value?.receiptId !== 'string' || result.value.file?.bytes !== file.size)
77
+ throw new Error('上传回执与所选文件不一致,请查看记录后重新选择。');
78
+ return result.value;
79
+ });
80
+ },
81
+ dispose() { disposed = true; stop(); listeners.clear(); },
82
+ };
83
+ }
package/src/index.mjs ADDED
@@ -0,0 +1,127 @@
1
+ import { opportunityInvocations, installOpportunityWeb } from './opportunity-host.mjs';
2
+ import { importInvocations, installImportWeb } from './import-host.mjs';
3
+ import { understandingInvocations, installUnderstandingWeb } from './understanding-host.mjs';
4
+ // The browser transport supplies identity; no model-facing control is registered here.
5
+ import { TypertRemoteService, RemoteError } from '@deepseek-ai/dsh-typert-protocol';
6
+ import { goalInvocations, installGoalWeb } from './goal-host.mjs';
7
+ import { modelInvocations, installModelWeb } from './model-host.mjs';
8
+ import { browseScope } from './browse-scope.mjs';
9
+ import { treeInvocations, installTreeWeb } from './tree-host.mjs';
10
+ import { desktopInvocations, installDesktopWeb } from './desktop-setup-host.mjs';
11
+ export const name = 'arcfold-method-ui';
12
+ export const inject = ['connection', 'typert', 'typertGateway'];
13
+ export const CHANNEL = '/api';
14
+ export const NAMESPACE = 'arcfoldMethods';
15
+ const SERVICE = 'arcfoldMethodWeb';
16
+ const ENDPOINTS = ['list', 'details', 'cards', 'cardDetails', 'notifications', 'acknowledge', 'decide'];
17
+
18
+ function invalid(message) {
19
+ throw Object.assign(new Error(message), { code: 'invalid_method_request' });
20
+ }
21
+ function object(value, allowed, required = []) {
22
+ if (!value || typeof value !== 'object' || Array.isArray(value) ||
23
+ Object.keys(value).some(key => !allowed.includes(key)) || required.some(key => !Object.hasOwn(value, key)))
24
+ invalid('方法请求包含缺失或未允许的字段。');
25
+ return value;
26
+ }
27
+ function identity(value) {
28
+ if (typeof value !== 'string' || !value.trim() || value.length > 512 || value.includes('\0'))
29
+ invalid('请求编号不能为空或超长。');
30
+ }
31
+ function page(payload, notifications = false, scoped = false) {
32
+ object(payload, notifications ? ['offset', 'limit', 'unread'] : scoped ? ['offset', 'limit', 'sessionId', 'viewScope'] : ['offset', 'limit']);
33
+ if (scoped) {
34
+ browseScope(payload.viewScope, 'all');
35
+ if (payload.viewScope !== undefined || payload.sessionId !== undefined) identity(payload.sessionId);
36
+ }
37
+ if (payload.offset !== undefined && (!Number.isSafeInteger(payload.offset) || payload.offset < 0) ||
38
+ payload.limit !== undefined && (!Number.isSafeInteger(payload.limit) || payload.limit < 1 || payload.limit > (notifications ? 200 : 100)) ||
39
+ payload.unread !== undefined && typeof payload.unread !== 'boolean') invalid('分页参数不合法。');
40
+ return payload;
41
+ }
42
+
43
+ export function createMethodHandler({ methods, connection }) {
44
+ return async (endpoint, payload, signal, peer) => {
45
+ try {
46
+ if (!peer || peer !== connection.browserPeer || peer.principal?.kind !== 'owner' ||
47
+ peer.interactions !== 'answer' || peer.signal?.aborted !== false || signal?.aborted !== false)
48
+ throw Object.assign(new Error('此操作只接受当前已认证网页的请求。'), { code: 'trusted_browser_required' });
49
+ let value;
50
+ switch (endpoint) {
51
+ case 'list': value = methods.listDecisions(page(payload)); break;
52
+ case 'cards': value = methods.listCards(page(payload, false, true)); break;
53
+ case 'cardDetails': {
54
+ object(payload, ['methodId', 'offset', 'limit', 'sessionId', 'viewScope'], ['methodId']); identity(payload.methodId);
55
+ const { methodId, ...pagination } = payload;
56
+ value = methods.cardDetails(methodId, page(pagination, false, true)); break;
57
+ }
58
+ case 'notifications': value = methods.notifications(page(payload, true)); break;
59
+ case 'details':
60
+ object(payload, ['methodId'], ['methodId']); identity(payload.methodId);
61
+ value = methods.details(payload.methodId); break;
62
+ case 'acknowledge':
63
+ object(payload, ['notificationId'], ['notificationId']); identity(payload.notificationId);
64
+ value = methods.acknowledgeNotification(payload.notificationId); break;
65
+ case 'decide': {
66
+ const fields = ['methodId', 'expectedRevision', 'contentHash', 'action', 'requestId'];
67
+ object(payload, fields, fields);
68
+ identity(payload.methodId); identity(payload.contentHash); identity(payload.requestId);
69
+ if (!Number.isSafeInteger(payload.expectedRevision) || payload.expectedRevision < 1 ||
70
+ !['continue', 'pause'].includes(payload.action)) invalid('决定必须绑定已查看的版本和操作。');
71
+ value = methods.decide({ ...payload, userIdentity: 'browser:' + peer.id });
72
+ break;
73
+ }
74
+ default: throw Object.assign(new Error('不存在的方法操作。'), { code: 'method_endpoint_not_found' });
75
+ }
76
+ return { ok: true, value: await value };
77
+ } catch (error) {
78
+ return { ok: false, error: { code: typeof error?.code === 'string' ? error.code : 'method_request_failed',
79
+ message: error instanceof Error ? error.message : '方法操作未完成。', details: {} } };
80
+ }
81
+ };
82
+ }
83
+
84
+ class MethodWeb extends TypertRemoteService {
85
+ constructor(ctx) { super(ctx, SERVICE, { namespace: NAMESPACE }); }
86
+ async dispatch(endpoint, request) {
87
+ const invocation = this.ctx.invocation;
88
+ const result = await createMethodHandler({ methods: this.ctx.arcfoldMethods, connection: this.ctx.connection })(
89
+ endpoint, request, invocation?.signal, invocation?.peer);
90
+ if (!result.ok) throw new RemoteError(result.error.code, result.error.message, result.error.details);
91
+ return result.value;
92
+ }
93
+ list(request) { return this.dispatch('list', request); }
94
+ details(request) { return this.dispatch('details', request); }
95
+ cards(request) { return this.dispatch('cards', request); }
96
+ cardDetails(request) { return this.dispatch('cardDetails', request); }
97
+ notifications(request) { return this.dispatch('notifications', request); }
98
+ acknowledge(request) { return this.dispatch('acknowledge', request); }
99
+ decide(request) { return this.dispatch('decide', request); }
100
+ }
101
+
102
+ class ArcfoldMode extends TypertRemoteService {
103
+ constructor(ctx) { super(ctx, 'arcfoldModeWeb', { namespace: 'arcfoldMode' }); }
104
+ current() {
105
+ const { peer, signal } = this.ctx.invocation ?? {};
106
+ if (!peer || peer !== this.ctx.connection.browserPeer || peer.principal?.kind !== 'owner' || peer.interactions !== 'answer' || peer.signal?.aborted !== false || signal?.aborted !== false) throw new RemoteError('TRUSTED_BROWSER_REQUIRED', '当前连接尚未认证。', {});
107
+ return { goalTreeV3: Boolean(this.ctx.get('arcfoldTreeMode')?.enabled) };
108
+ }
109
+ }
110
+ export function apply(ctx) {
111
+ new ArcfoldMode(ctx);
112
+ ctx.inject(['arcfoldMethods'], owner => { new MethodWeb(owner); });
113
+ installTreeWeb(ctx);
114
+ installDesktopWeb(ctx);
115
+ installGoalWeb(ctx);
116
+ installModelWeb(ctx);
117
+ installUnderstandingWeb(ctx);
118
+ installImportWeb(ctx);
119
+ installOpportunityWeb(ctx);
120
+ // Use the existing authenticated Gateway; do not claim its single /api interceptor.
121
+ ctx.typert.register({ package: '@arcfold/dsh-method-ui', face: 'host', schemas: [],
122
+ model: { services: [], events: [], objects: [] },
123
+ invocations: [{ id: '@arcfold/dsh-method-ui#arcfoldMode/current', service: 'arcfoldModeWeb', namespace: 'arcfoldMode', method: 'current', invocation: { kind: 'direct' }, parameters: [], result: { mode: 'src-json' } }, ...desktopInvocations, ...treeInvocations, ...ENDPOINTS.map(method => ({ id: `@arcfold/dsh-method-ui#${NAMESPACE}/${method}`,
124
+ service: SERVICE, namespace: NAMESPACE, method, invocation: { kind: 'direct' },
125
+ parameters: [{ name: 'request', wire: 'request', source: 'json', codec: { mode: 'src-json' } }],
126
+ result: { mode: 'src-json' } })), ...goalInvocations, ...modelInvocations, ...understandingInvocations, ...importInvocations, ...opportunityInvocations] });
127
+ }
@@ -0,0 +1,153 @@
1
+ export const zh = {
2
+ collaborationTitle: 'Arcfold', collaborationClose: '关闭',
3
+ collaborationViewScope: '查看范围', collaborationAll: '全部积累', collaborationCurrent: '与当前聊天相关',
4
+ browseOtherSession: '来自其他聊天', browseReturnToOrigin: '请回到原聊天修改这条记录或继续相关工作。',
5
+ presentationUnknown: "暂未识别",
6
+ stateTrial: "试用中",
7
+ stateActive: "采用中",
8
+ statePaused: "已暂停",
9
+ stateUnavailable: '记录暂不可用',
10
+ stateSuperseded: "已有后继版本",
11
+ stateWithdrawn: "已停止采用",
12
+ stateReserved: "已领取,待核对",
13
+ stateSettled: "已核对",
14
+ roleUser: "用户",
15
+ roleAssistant: "助手",
16
+ roleTool: "工具",
17
+ roleProgram: "程序",
18
+ outcomePending: "尚待结束",
19
+ outcomeCompleted: "本轮已完成",
20
+ outcomeCancelled: "本轮已取消",
21
+ outcomeFailed: "本轮失败",
22
+ outcomeInterrupted: "本轮中断",
23
+ outcomeUnknown: "本轮结果未知",
24
+ admissionAccepted: "原生入口已受理",
25
+ admissionFailed: "提交失败",
26
+ admissionUnknown: "提交结果未知",
27
+ actionStopUnderstanding: "停止采用理解",
28
+ actionWithdrawSource: "撤回来源",
29
+ actionCorrect: "提交逐字纠正",
30
+
31
+ title: '协作方法', pending: '待决定', unread: '未读提醒', loading: '正在读取方法…', empty: '此范围内暂无协作方法。',
32
+ decisionPrompt: '需要你决定:是否继续采用此版本。', reviewDecision: '查看并决定', viewDetails: '查看详情',
33
+ history: '历史提醒', backToMethods: '返回方法列表', keepPaused: '保持暂停',
34
+ open: '查看全文与理由', close: '收起详情', refresh: '刷新', previous: '上一页', next: '下一页',
35
+ guidance: '完整指导', applies: '适用条件', excludes: '不适用条件', sources: '依据原件', reason: '当前状态与理由',
36
+ version: '内容版本', revision: '记录版本', status: '状态', usage: '已选入试用', remaining: '剩余次数',
37
+ pendingAdoptions: '尚待核对的采用', conversation: '交流', turn: '轮次',
38
+ successors: '后继版本', continue: '继续采用此版本', pause: '暂停采用此版本', busy: '正在保存…',
39
+ saved: '决定已保存。继续采用不代表效果已经验证,也不增加工作或工具权限。',
40
+ failed: '操作未完成。', unknown: '结果尚未确认;可再次点击同一操作,沿用原请求编号。',
41
+ conflict: '方法已变化。请重新查看以下全文和理由,再决定是否提交。',
42
+ notifications: '停止与到期提醒', read: '已读', acknowledge: '标记已读', noNotifications: '目前没有提醒。',
43
+ noSources: '没有可显示的原件。', sourceIdentity: '原件身份', sourceTime: '原始时间', author: '作者角色',
44
+ provenance: '来源归属', retry: '重新读取详情', boundary: '查看与已读不会恢复方法;只有下方按钮会保存你的决定。',
45
+ };
46
+ export const en = {
47
+ collaborationTitle: 'Arcfold', collaborationClose: 'Close',
48
+ collaborationViewScope: 'View scope', collaborationAll: 'All records', collaborationCurrent: 'Related to this conversation',
49
+ browseOtherSession: 'From another conversation', browseReturnToOrigin: 'Return to the original conversation to change this record or continue its work.',
50
+ presentationUnknown: "Not yet recognized",
51
+ stateTrial: "In trial",
52
+ stateActive: "In use",
53
+ statePaused: "Paused",
54
+ stateUnavailable: 'Record unavailable',
55
+ stateSuperseded: "Superseded",
56
+ stateWithdrawn: "Withdrawn",
57
+ stateReserved: "Reserved; awaiting reconciliation",
58
+ stateSettled: "Reconciled",
59
+ roleUser: "User",
60
+ roleAssistant: "Assistant",
61
+ roleTool: "Tool",
62
+ roleProgram: "Program",
63
+ outcomePending: "Awaiting turn end",
64
+ outcomeCompleted: "Turn completed",
65
+ outcomeCancelled: "Turn cancelled",
66
+ outcomeFailed: "Turn failed",
67
+ outcomeInterrupted: "Turn interrupted",
68
+ outcomeUnknown: "Turn outcome unknown",
69
+ admissionAccepted: "Accepted by native input",
70
+ admissionFailed: "Submission failed",
71
+ admissionUnknown: "Submission outcome unknown",
72
+ actionStopUnderstanding: "Stop using understanding",
73
+ actionWithdrawSource: "Withdraw source",
74
+ actionCorrect: "Submit exact correction",
75
+
76
+ title: 'Collaboration methods', pending: 'Awaiting decision', unread: 'Unread notices', loading: 'Loading methods…', empty: 'No collaboration methods in this view.',
77
+ decisionPrompt: 'Your decision: whether to keep using this version.', reviewDecision: 'Review and decide', viewDetails: 'View details',
78
+ history: 'Notice history', backToMethods: 'Back to methods', keepPaused: 'Keep paused',
79
+ open: 'Read full method and reason', close: 'Close details', refresh: 'Refresh', previous: 'Previous', next: 'Next',
80
+ guidance: 'Full guidance', applies: 'Applies when', excludes: 'Does not apply when', sources: 'Original evidence', reason: 'Current state and reason',
81
+ version: 'Content version', revision: 'Record revision', status: 'State', usage: 'Trial selections', remaining: 'Remaining',
82
+ pendingAdoptions: 'Selections awaiting reconciliation', conversation: 'Conversation', turn: 'Turn',
83
+ successors: 'Later versions', continue: 'Continue this version', pause: 'Pause this version', busy: 'Saving…',
84
+ saved: 'Decision saved. Continuing does not validate effectiveness or grant additional work or tool permissions.',
85
+ failed: 'The action did not complete.', unknown: 'Outcome not confirmed; repeat the same action to reuse its request ID.',
86
+ conflict: 'The method changed. Read the refreshed guidance and reason before deciding again.',
87
+ notifications: 'Pause and trial notices', read: 'Read', acknowledge: 'Mark read', noNotifications: 'No notices.',
88
+ noSources: 'No original evidence is available to display.', sourceIdentity: 'Source identity', sourceTime: 'Original time', author: 'Author role',
89
+ provenance: 'Provenance', retry: 'Reload details', boundary: 'Viewing or marking read does not resume a method. Only these controls save your decision.',
90
+ };
91
+
92
+ const presentationKeys = {
93
+ "method": {
94
+ "trial": "stateTrial",
95
+ "active": "stateActive",
96
+ "paused": "statePaused",
97
+ "unavailable": "stateUnavailable",
98
+ "superseded": "stateSuperseded"
99
+ },
100
+ "understanding": {
101
+ "active": "stateActive",
102
+ "withdrawn": "stateWithdrawn",
103
+ "superseded": "stateSuperseded"
104
+ },
105
+ "adoption": {
106
+ "reserved": "stateReserved",
107
+ "settled": "stateSettled"
108
+ },
109
+ "role": {
110
+ "user": "roleUser",
111
+ "assistant": "roleAssistant",
112
+ "tool": "roleTool",
113
+ "program": "roleProgram"
114
+ },
115
+ "outcome": {
116
+ "pending": "outcomePending",
117
+ "completed": "outcomeCompleted",
118
+ "cancelled": "outcomeCancelled",
119
+ "failed": "outcomeFailed",
120
+ "interrupted": "outcomeInterrupted",
121
+ "unknown": "outcomeUnknown"
122
+ },
123
+ "admission": {
124
+ "accepted": "admissionAccepted",
125
+ "failed": "admissionFailed",
126
+ "unknown": "admissionUnknown"
127
+ },
128
+ "understandingAction": {
129
+ "stop-understanding": "actionStopUnderstanding",
130
+ "withdraw-source": "actionWithdrawSource",
131
+ "correct": "actionCorrect"
132
+ },
133
+ "goal": {
134
+ "tracking": "goalTracking",
135
+ "paused": "goalPaused",
136
+ "closed": "goalClosed"
137
+ },
138
+ "relation": {
139
+ "part_of": "goalPart",
140
+ "supports": "goalSupports",
141
+ "derived_from": "goalDerived"
142
+ },
143
+ "claim": {
144
+ "user_statement": "understandingLiteral",
145
+ "inference": "understandingInference",
146
+ "open_question": "understandingQuestion"
147
+ }
148
+ };
149
+ // Free text (including reasons, provenance and authored content) is not passed here.
150
+ export function presentationLabel(group, value, t) {
151
+ const entries = Object.hasOwn(presentationKeys, group) ? presentationKeys[group] : null;
152
+ return t(entries && Object.hasOwn(entries, value) ? entries[value] : 'presentationUnknown');
153
+ }
@@ -0,0 +1,7 @@
1
+ // Browsing checks the currently mounted main Agent, never historical work or
2
+ // client-supplied preset labels. This must not wake or bind a cold session.
3
+ export function currentMainAgent({ agents, agentPresets }, sessionId) {
4
+ const agent = agents?.get(sessionId);
5
+ return agent && agent.id === sessionId && agentPresets?.composedPreset(agent.ctx) === 'arcfold-main'
6
+ ? agent : null;
7
+ }
@@ -0,0 +1,82 @@
1
+ import { requireViewScope, viewScopeFields } from './browse-source.mjs';
2
+ const CHANNEL = '/api';
3
+ export async function callMethod(rpc, endpoint, payload, signal) {
4
+ const result = await rpc.call(CHANNEL, `arcfoldMethods/${endpoint}`, { args: { request: payload } }, signal);
5
+ if (result?.ok !== true) throw Object.assign(new Error(result?.error?.message || '方法请求未完成。'),
6
+ { code: result?.error?.code || 'method_request_failed', remote: true });
7
+ return result.value;
8
+ }
9
+
10
+ // One UI-owned read cache, not a second business queue. It exists only while observed.
11
+ export function createMethodSource({ rpc, sessionId, viewScope, subscribeReset, intervalMs = 5000 }) {
12
+ viewScopeFields(viewScope);
13
+ let snapshot = { loading: true, error: null, items: [], total: 0, decisionCount: 0,
14
+ nextOffset: null, offset: 0, unreadCount: 0, readEpoch: 0 };
15
+ const listeners = new Set(), reads = new Set();
16
+ let generation = 0, controller, timer, reset, disposed = false;
17
+ const publish = value => { snapshot = { ...snapshot, ...value }; for (const listener of listeners) listener(); };
18
+ const scope = () => viewScope === undefined ? {} : { sessionId, ...viewScopeFields(viewScope) };
19
+ const cancelled = () => Object.assign(new Error('查看范围已变化,请重新打开方法。'), { code: 'method_read_cancelled' });
20
+ function invalidateReads() {
21
+ controller?.abort(); generation++;
22
+ for (const read of reads) read.abort();
23
+ publish({ loading: true, error: null, items: [], total: 0, decisionCount: 0,
24
+ nextOffset: null, offset: 0, unreadCount: 0, readEpoch: snapshot.readEpoch + 1 });
25
+ }
26
+ async function refresh(offset = snapshot.offset) {
27
+ if (disposed) return;
28
+ controller?.abort(); controller = new AbortController();
29
+ const current = ++generation, signal = controller.signal;
30
+ try {
31
+ const cards = await callMethod(rpc, 'cards', { offset, limit: 20, ...scope() }, signal);
32
+ if (current !== generation || signal.aborted || disposed) return;
33
+ publish({ loading: false, error: null, items: cards.items, total: cards.total,
34
+ decisionCount: cards.decisionCount, nextOffset: cards.nextOffset, offset, unreadCount: cards.unreadCount });
35
+ } catch (error) {
36
+ if (current === generation && !signal.aborted && !disposed)
37
+ publish({ loading: false, error: error instanceof Error ? error.message : '读取方法失败。' });
38
+ }
39
+ }
40
+ function stop() {
41
+ clearInterval(timer); timer = undefined; reset?.(); reset = undefined;
42
+ invalidateReads();
43
+ }
44
+ return {
45
+ getSnapshot: () => snapshot,
46
+ subscribe(listener) {
47
+ if (disposed) return () => {};
48
+ listeners.add(listener);
49
+ if (listeners.size === 1) {
50
+ void refresh();
51
+ timer = setInterval(() => { void refresh(); }, intervalMs);
52
+ reset = subscribeReset(() => { invalidateReads(); void refresh(0); });
53
+ }
54
+ return () => { listeners.delete(listener); if (!listeners.size) stop(); };
55
+ },
56
+ refresh,
57
+ setViewScope(value) {
58
+ requireViewScope(value);
59
+ if (disposed || viewScope === value) return;
60
+ viewScope = value; invalidateReads();
61
+ if (listeners.size) void refresh(0);
62
+ },
63
+ async details(methodId, offset = 0) {
64
+ if (disposed) throw cancelled();
65
+ const read = new AbortController(), epoch = snapshot.readEpoch; reads.add(read);
66
+ try {
67
+ const value = await callMethod(rpc, 'cardDetails', { methodId, offset, limit: 20, ...scope() }, read.signal);
68
+ if (disposed || read.signal.aborted || epoch !== snapshot.readEpoch) throw cancelled();
69
+ return value;
70
+ } finally { reads.delete(read); }
71
+ },
72
+ async acknowledge(notificationId) {
73
+ const result = await callMethod(rpc, 'acknowledge', { notificationId });
74
+ await refresh(); return result;
75
+ },
76
+ async decide(request) {
77
+ const result = await callMethod(rpc, 'decide', request);
78
+ await refresh(); return result;
79
+ },
80
+ dispose() { disposed = true; stop(); listeners.clear(); },
81
+ };
82
+ }