@undefineds.co/models 0.2.16 → 0.2.18

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.
package/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # LinX 数据模型
2
2
 
3
3
  > 基于 Solid Pod 标准的 LinX 核心数据模型定义
4
- >
4
+ >
5
5
  > 使用 `drizzle-solid` ORM,兼容标准 RDF 词汇表(VCARD, FOAF, SIOC, DCTerms 等)
6
6
 
7
7
  ---
@@ -128,26 +128,26 @@ LinX 遵循 Solid 生态的最佳实践,优先使用标准 RDF 词汇表:
128
128
  givenName: string; // vcard:givenName
129
129
  familyName: string; // vcard:familyName
130
130
  nickname: string; // foaf:nick
131
-
131
+
132
132
  // 联系方式
133
133
  email: string; // vcard:hasEmail
134
134
  telephone: string; // vcard:hasTelephone
135
135
  mobile: string;
136
-
136
+
137
137
  // 地址
138
138
  homeAddress: string; // vcard:hasAddress (JSON)
139
139
  workAddress: string;
140
-
140
+
141
141
  // 组织
142
142
  organization: string; // vcard:organizationName
143
143
  title: string; // vcard:title
144
-
144
+
145
145
  // Solid 身份
146
146
  webId: string; // foaf:weblog
147
-
147
+
148
148
  // AI 联系人
149
149
  aiAssistantId: string; // linx:aiAssistant
150
-
150
+
151
151
  // 关系和标签
152
152
  relationship: string; // vcard:hasRelated
153
153
  tags: string; // dcterms:subject (JSON)
@@ -205,7 +205,7 @@ LinX 遵循 Solid 生态的最佳实践,优先使用标准 RDF 词汇表:
205
205
  modifiedAt: timestamp; // dcterms:modified (编辑时间)
206
206
  deletedAt: timestamp; // linx:deletedAt (软删除)
207
207
  readBy: string; // linx:readBy (JSON)
208
-
208
+
209
209
  // 附件(如果是文件/图片消息)
210
210
  attachmentUri: string; // schema:about
211
211
  attachmentName: string; // schema:name
@@ -260,12 +260,12 @@ LinX 遵循 Solid 生态的最佳实践,优先使用标准 RDF 词汇表:
260
260
  description: string; // dcterms:description
261
261
  favoriteType: string; // linx:favoriteType
262
262
  targetUri: string; // linx:favoriteTarget ⭐ 必需
263
-
263
+
264
264
  // 快照(避免查询原始资源)
265
265
  snapshotContent: string; // schema:text
266
266
  snapshotAuthor: string; // schema:author
267
267
  snapshotCreatedAt: timestamp; // schema:dateCreated
268
-
268
+
269
269
  owner: string; // dcterms:creator
270
270
  folder: string; // linx:conversation
271
271
  tags: string; // dcterms:subject (JSON)
@@ -350,37 +350,37 @@ POD_CACHE_SIZE: "pod.cacheSize"
350
350
  description: string; // dcterms:description
351
351
  avatarUrl: string; // foaf:depiction
352
352
  assistantType: string; // dcterms:type (system, custom, shared)
353
-
353
+
354
354
  // 模型配置
355
355
  provider: string; // linx:aiProvider (openai, anthropic, ollama, custom)
356
356
  modelId: string; // linx:aiModel (gpt-4, claude-3, llama2)
357
357
  systemPrompt: string; // linx:systemPrompt
358
-
358
+
359
359
  // 模型参数
360
360
  temperature: float; // linx:temperature (0-2)
361
361
  maxTokens: integer; // linx:maxTokens
362
362
  topP: float;
363
363
  frequencyPenalty: float;
364
364
  presencePenalty: float;
365
-
365
+
366
366
  // 功能配置
367
367
  enableStreaming: boolean;
368
368
  enableFunctionCalling: boolean;
369
369
  allowedFunctions: string; // JSON 数组
370
-
370
+
371
371
  // Pod 访问权限
372
372
  podAccessLevel: string; // linx:status (read, write, full)
373
373
  allowedContainers: string; // JSON 数组
374
-
374
+
375
375
  // 共享
376
376
  owner: string; // dcterms:creator
377
377
  isPublic: boolean;
378
378
  sharedWith: string; // JSON
379
-
379
+
380
380
  // 统计
381
381
  messageCount: integer;
382
382
  lastUsedAt: timestamp;
383
-
383
+
384
384
  status: string; // linx:status (active, disabled, archived)
385
385
  createdAt: timestamp;
386
386
  modifiedAt: timestamp;
@@ -407,7 +407,7 @@ yarn workspace @linq/models install
407
407
  import {
408
408
  // 词汇表
409
409
  LINQ, SIOC, DCTerms, SCHEMA,
410
-
410
+
411
411
  // 模型表
412
412
  contactTable,
413
413
  chatTable,
@@ -416,12 +416,12 @@ import {
416
416
  favoriteTable,
417
417
  settingsTable,
418
418
  aiAssistantTable,
419
-
419
+
420
420
  // 类型
421
421
  type ContactRow,
422
422
  type ChatRow,
423
423
  type MessageRow,
424
-
424
+
425
425
  // 常量
426
426
  CONTACT_TYPES,
427
427
  SETTING_KEYS,
@@ -591,7 +591,3 @@ yarn workspace @linq/models typecheck
591
591
  ## 许可证
592
592
 
593
593
  MIT License
594
-
595
-
596
-
597
-
@@ -1,4 +1,4 @@
1
- export declare const agentRepository: import("./repository").PodRepositoryDescriptor<import("@undefineds.co/drizzle-solid/dist/core/schema").PodTableWithColumns<import("@undefineds.co/drizzle-solid/dist/core/schema").ResolvedColumns<{
1
+ export declare const agentRepository: import("@undefineds.co/drizzle-solid/dist/core/repository").PodRepositoryDescriptor<import("@undefineds.co/drizzle-solid/dist/core/schema").PodTableWithColumns<import("@undefineds.co/drizzle-solid/dist/core/schema").ResolvedColumns<{
2
2
  id: import("@undefineds.co/drizzle-solid/dist/core/schema").PodStringColumn<false, false>;
3
3
  name: import("@undefineds.co/drizzle-solid/dist/core/schema").ColumnBuilder<"string", null, true, false>;
4
4
  description: import("@undefineds.co/drizzle-solid/dist/core/schema").ColumnBuilder<"string", null, false, false>;
@@ -53,13 +53,14 @@ export declare const UNDEFINEDS_AI_MODEL_IDS: readonly ["linx-lite", "linx"];
53
53
  export declare function getAIConfigProviderCatalog(): readonly AIConfigProviderCatalogEntry[];
54
54
  export declare function getAIConfigProviderMetadata(providerId: string): AIConfigProviderCatalogEntry;
55
55
  export declare function normalizeAIConfigResourceId(raw?: string | null): string;
56
+ export declare function normalizeAIConfigModelId(raw?: string | null, providerId?: string | null): string;
56
57
  export declare function normalizeAIConfigProviderId(raw?: string | null): string;
57
58
  export declare function sameAIConfigProviderFamily(left?: string | null, right?: string | null): boolean;
58
59
  export declare function getAIConfigProviderFamilyIds(providerId: string): string[];
59
60
  export declare function getAIConfigDefaultBaseUrl(providerId: string): string | undefined;
60
61
  export declare function getDefaultAIConfigCredentialId(providerId: string): string;
61
- export declare function aiConfigProviderUri(providerId: string): string;
62
- export declare function aiConfigModelUri(modelId: string): string;
62
+ export declare function aiConfigProviderRef(providerId: string): string;
63
+ export declare function aiConfigModelRef(providerId: string, modelId?: string): string;
63
64
  export declare function buildAIConfigProviderStateMap(options: BuildAIConfigProviderStateMapOptions): Record<string, AIConfigProviderState>;
64
65
  export declare function buildAIConfigMutationPlan(input: {
65
66
  providerId: string;
@@ -6,12 +6,6 @@ export const LINX_MODEL_ID = 'linx';
6
6
  export const DEFAULT_LINX_MODEL_ID = LINX_LITE_MODEL_ID;
7
7
  export const UNDEFINEDS_AI_MODEL_IDS = [LINX_LITE_MODEL_ID, LINX_MODEL_ID];
8
8
  const AI_CONFIG_PROVIDER_CATALOG = [
9
- {
10
- id: UNDEFINEDS_AI_PROVIDER_ID,
11
- displayName: UNDEFINEDS_AI_PROVIDER_DISPLAY_NAME,
12
- defaultBaseUrl: UNDEFINEDS_AI_BASE_URL,
13
- defaultModels: [...UNDEFINEDS_AI_MODEL_IDS],
14
- },
15
9
  {
16
10
  id: 'openai',
17
11
  displayName: 'OpenAI',
@@ -82,6 +76,7 @@ const AI_CONFIG_PROVIDER_CATALOG = [
82
76
  },
83
77
  ];
84
78
  const AI_CONFIG_PROVIDER_MAP = new Map(AI_CONFIG_PROVIDER_CATALOG.map((entry) => [entry.id, entry]));
79
+ const ABSOLUTE_IRI = /^[a-zA-Z][a-zA-Z\d+.-]*:/;
85
80
  function normalizeText(value) {
86
81
  return value.trim().toLowerCase();
87
82
  }
@@ -124,12 +119,34 @@ export function getAIConfigProviderMetadata(providerId) {
124
119
  export function normalizeAIConfigResourceId(raw) {
125
120
  if (!raw)
126
121
  return '';
127
- if (raw.includes('#'))
128
- return raw.split('#').pop() || raw;
129
- const clean = raw.replace(/\/$/, '');
122
+ const value = raw.trim();
123
+ if (!value)
124
+ return '';
125
+ if (value.includes('#'))
126
+ return value.split('#').pop() || value;
127
+ if (!ABSOLUTE_IRI.test(value) && !value.endsWith('.ttl')) {
128
+ return value;
129
+ }
130
+ const clean = value.replace(/\/$/, '');
131
+ if (!ABSOLUTE_IRI.test(value)) {
132
+ return clean.endsWith('.ttl') ? clean.slice(0, -4) : clean;
133
+ }
130
134
  const tail = clean.split('/').pop() || clean;
131
135
  return tail.endsWith('.ttl') ? tail.slice(0, -4) : tail;
132
136
  }
137
+ export function normalizeAIConfigModelId(raw, providerId) {
138
+ const modelId = normalizeAIConfigResourceId(raw);
139
+ if (!modelId.includes('/'))
140
+ return modelId;
141
+ const [prefix, ...rest] = modelId.split('/');
142
+ if (rest.length === 0)
143
+ return modelId;
144
+ if (!providerId)
145
+ return modelId;
146
+ return normalizeAIConfigProviderId(prefix) === normalizeAIConfigProviderId(providerId)
147
+ ? rest.join('/')
148
+ : modelId;
149
+ }
133
150
  export function normalizeAIConfigProviderId(raw) {
134
151
  const normalized = normalizeText(normalizeAIConfigResourceId(raw));
135
152
  if (!normalized)
@@ -141,6 +158,9 @@ export function normalizeAIConfigProviderId(raw) {
141
158
  }
142
159
  return normalized;
143
160
  }
161
+ function normalizeAIConfigModelStorageId(raw, providerId) {
162
+ return normalizeAIConfigModelId(raw, providerId);
163
+ }
144
164
  export function sameAIConfigProviderFamily(left, right) {
145
165
  const normalizedLeft = normalizeAIConfigProviderId(left);
146
166
  const normalizedRight = normalizeAIConfigProviderId(right);
@@ -156,11 +176,16 @@ export function getAIConfigDefaultBaseUrl(providerId) {
156
176
  export function getDefaultAIConfigCredentialId(providerId) {
157
177
  return `${normalizeAIConfigProviderId(providerId)}-default`;
158
178
  }
159
- export function aiConfigProviderUri(providerId) {
160
- return `/settings/ai/providers.ttl#${normalizeAIConfigProviderId(providerId)}`;
179
+ export function aiConfigProviderRef(providerId) {
180
+ return normalizeAIConfigProviderId(providerId);
161
181
  }
162
- export function aiConfigModelUri(modelId) {
163
- return `/settings/ai/models.ttl#${normalizeAIConfigResourceId(modelId)}`;
182
+ export function aiConfigModelRef(providerId, modelId) {
183
+ if (modelId === undefined) {
184
+ return normalizeAIConfigResourceId(providerId);
185
+ }
186
+ const provider = normalizeAIConfigProviderId(providerId);
187
+ const model = normalizeAIConfigModelStorageId(modelId, provider);
188
+ return provider && model ? `/settings/ai/models/${provider}.ttl#${model}` : model;
164
189
  }
165
190
  export function buildAIConfigProviderStateMap(options) {
166
191
  const catalog = options.catalog ?? AI_CONFIG_PROVIDER_CATALOG;
@@ -188,7 +213,7 @@ export function buildAIConfigProviderStateMap(options) {
188
213
  const providerId = normalizeAIConfigProviderId(String(row.isProvidedBy ?? ''));
189
214
  if (!providerId)
190
215
  continue;
191
- const modelId = normalizeAIConfigResourceId(String(row.id ?? row['@id'] ?? ''));
216
+ const modelId = normalizeAIConfigModelStorageId(String(row.id ?? row['@id'] ?? ''), providerId);
192
217
  if (!modelId)
193
218
  continue;
194
219
  const list = modelMap.get(providerId) ?? [];
@@ -226,7 +251,7 @@ export function buildAIConfigProviderStateMap(options) {
226
251
  enabled: true,
227
252
  capabilities: [],
228
253
  }));
229
- const selectedModelId = normalizeAIConfigResourceId(typeof providerRow?.hasModel === 'string' ? providerRow.hasModel : '') || preferredSelectedModelId(models);
254
+ const selectedModelId = normalizeAIConfigModelStorageId(typeof providerRow?.hasModel === 'string' ? providerRow.hasModel : '', providerId) || preferredSelectedModelId(models);
230
255
  states[providerId] = {
231
256
  id: providerId,
232
257
  enabled: (typeof credentialRow?.status === 'string' ? credentialRow.status : 'inactive') === 'active',
@@ -254,21 +279,21 @@ export function buildAIConfigMutationPlan(input) {
254
279
  if (hasConfigUpdate || input.updates.models !== undefined) {
255
280
  const selectedModelId = input.updates.models
256
281
  ? preferredSelectedModelId(input.updates.models)
257
- : normalizeAIConfigResourceId(typeof existingProvider?.hasModel === 'string' ? existingProvider.hasModel : '');
282
+ : normalizeAIConfigModelStorageId(typeof existingProvider?.hasModel === 'string' ? existingProvider.hasModel : '', providerId);
258
283
  providerPayload = {
259
284
  id: providerId,
260
285
  baseUrl: input.updates.baseUrl ??
261
286
  (typeof existingProvider?.baseUrl === 'string' ? existingProvider.baseUrl : undefined) ??
262
287
  metadata.defaultBaseUrl,
263
288
  proxyUrl: typeof existingProvider?.proxyUrl === 'string' ? existingProvider.proxyUrl : undefined,
264
- hasModel: selectedModelId ? aiConfigModelUri(selectedModelId) : undefined,
289
+ hasModel: selectedModelId ? aiConfigModelRef(providerId, selectedModelId) : undefined,
265
290
  };
266
291
  }
267
292
  if (hasConfigUpdate) {
268
293
  credentialPayload = {
269
294
  id: normalizeAIConfigResourceId(typeof existingCredential?.id === 'string' ? existingCredential.id : '') ||
270
295
  getDefaultAIConfigCredentialId(providerId),
271
- provider: aiConfigProviderUri(providerId),
296
+ provider: aiConfigProviderRef(providerId),
272
297
  service: typeof existingCredential?.service === 'string' && existingCredential.service ? existingCredential.service : 'ai',
273
298
  status: input.updates.enabled !== undefined
274
299
  ? input.updates.enabled
@@ -288,12 +313,12 @@ export function buildAIConfigMutationPlan(input) {
288
313
  }
289
314
  if (input.updates.models !== undefined) {
290
315
  const existingById = new Map(existingModels.map((row) => [
291
- normalizeAIConfigResourceId(String(row.id ?? row['@id'] ?? '')),
316
+ normalizeAIConfigModelStorageId(String(row.id ?? row['@id'] ?? ''), providerId),
292
317
  row,
293
318
  ]));
294
319
  const nextIds = new Set();
295
320
  for (const model of input.updates.models) {
296
- const modelId = normalizeAIConfigResourceId(model.id);
321
+ const modelId = normalizeAIConfigModelStorageId(model.id, providerId);
297
322
  if (!modelId)
298
323
  continue;
299
324
  nextIds.add(modelId);
@@ -303,7 +328,7 @@ export function buildAIConfigMutationPlan(input) {
303
328
  id: modelId,
304
329
  displayName: model.name || modelId,
305
330
  modelType: 'chat',
306
- isProvidedBy: aiConfigProviderUri(providerId),
331
+ isProvidedBy: aiConfigProviderRef(providerId),
307
332
  status: model.enabled ? 'active' : 'inactive',
308
333
  createdAt: existingDate(existing?.createdAt) ?? now,
309
334
  updatedAt: now,
@@ -1,17 +1,18 @@
1
1
  import { id, integer, podTable, string, timestamp, uri } from "@undefineds.co/drizzle-solid";
2
+ import { aiProviderTable } from "./ai-provider.schema.js";
2
3
  import { XPOD_AI } from "./namespaces.js";
3
4
  export const aiModelTable = podTable("aiModel", {
4
5
  id: id("id"),
5
6
  displayName: string("displayName").predicate(XPOD_AI.displayName),
6
7
  modelType: string("modelType").predicate(XPOD_AI.modelType).default("chat"),
7
- isProvidedBy: uri("isProvidedBy").predicate(XPOD_AI.isProvidedBy),
8
+ isProvidedBy: uri("isProvidedBy").predicate(XPOD_AI.isProvidedBy).link(aiProviderTable),
8
9
  dimension: integer("dimension").predicate(XPOD_AI.dimension),
9
10
  status: string("status").predicate(XPOD_AI.status).default("active"),
10
11
  createdAt: timestamp("createdAt").predicate(XPOD_AI.createdAt).notNull().defaultNow(),
11
12
  updatedAt: timestamp("updatedAt").predicate(XPOD_AI.updatedAt).notNull().defaultNow(),
12
13
  }, {
13
- base: "/settings/ai/models.ttl",
14
+ base: "/settings/ai/models/",
14
15
  type: XPOD_AI.Model,
15
16
  namespace: XPOD_AI,
16
- subjectTemplate: "#{id}",
17
+ subjectTemplate: "{isProvidedBy|id}.ttl#{id}",
17
18
  });
@@ -4,7 +4,7 @@ export const aiProviderTable = podTable("aiProvider", {
4
4
  id: id("id"),
5
5
  baseUrl: string("baseUrl").predicate(XPOD_AI.baseUrl),
6
6
  proxyUrl: string("proxyUrl").predicate(XPOD_AI.proxyUrl),
7
- hasModel: uri("hasModel").predicate(XPOD_AI.hasModel),
7
+ hasModel: uri("hasModel").predicate(XPOD_AI.hasModel).link("aiModel"),
8
8
  }, {
9
9
  base: "/settings/ai/providers.ttl",
10
10
  type: XPOD_AI.Provider,
@@ -1,4 +1,4 @@
1
- export declare function buildApprovalSubjectPath(approvalId: string, createdAt?: Date | string | number): string;
1
+ export declare function extractApprovalIdFromApprovalRef(approvalRef: string | null | undefined): string | null;
2
2
  export declare const approvalResource: import("@undefineds.co/drizzle-solid/dist/core/schema").PodTableWithColumns<import("@undefineds.co/drizzle-solid/dist/core/schema").ResolvedColumns<{
3
3
  id: import("@undefineds.co/drizzle-solid/dist/core/schema").PodStringColumn<false, false>;
4
4
  session: import("@undefineds.co/drizzle-solid/dist/core/schema").ColumnBuilder<"uri", null, true, false>;
@@ -1,12 +1,7 @@
1
- import { podTable, uri, string, text, timestamp, id } from '@undefineds.co/drizzle-solid';
1
+ import { extractPodResourceTemplateValue, podTable, uri, string, text, timestamp, id } from '@undefineds.co/drizzle-solid';
2
2
  import { ODRL, UDFS, DCTerms } from './namespaces.js';
3
- export function buildApprovalSubjectPath(approvalId, createdAt = new Date()) {
4
- const date = createdAt instanceof Date ? createdAt : new Date(createdAt);
5
- const safeDate = Number.isFinite(date.getTime()) ? date : new Date();
6
- const yyyy = String(safeDate.getUTCFullYear());
7
- const mm = String(safeDate.getUTCMonth() + 1).padStart(2, '0');
8
- const dd = String(safeDate.getUTCDate()).padStart(2, '0');
9
- return `/.data/approvals/${yyyy}/${mm}/${dd}.ttl#${encodeURIComponent(approvalId)}`;
3
+ export function extractApprovalIdFromApprovalRef(approvalRef) {
4
+ return extractPodResourceTemplateValue(approvalResource, approvalRef);
10
5
  }
11
6
  // Approval request resource (separate from Solid inbox notifications).
12
7
  export const approvalResource = podTable('approval', {
@@ -0,0 +1,23 @@
1
+ import type { ApprovalRow } from './approval.schema';
2
+ import type { AuditRow } from './audit.schema';
3
+ export interface AuditPresentation {
4
+ title: string;
5
+ description: string;
6
+ category: 'auth_required' | 'audit';
7
+ status?: string;
8
+ chatId: string | null;
9
+ threadId: string | null;
10
+ thread: string | null;
11
+ about: string | null;
12
+ authUrl: string | null;
13
+ authMethod: string | null;
14
+ authMessage: string | null;
15
+ actorRoleLabel: string;
16
+ }
17
+ type RelatedApproval = Pick<ApprovalRow, 'target' | 'toolName' | 'risk' | 'reason' | 'status' | 'context'> | null | undefined;
18
+ export declare function buildAuditDetailRecord(audit: AuditRow, relatedApproval?: RelatedApproval): Record<string, unknown>;
19
+ export declare function formatInboxStatusLabel(status?: string | null): string | null;
20
+ export declare function formatAuditActorRole(role?: string | null): string;
21
+ export declare function createResolvedAuthTimestampsIndex(audits: AuditRow[]): Map<string, number[]>;
22
+ export declare function buildAuditPresentation(audit: AuditRow, resolvedAuthTimestampsByKey: Map<string, number[]>, relatedApproval?: RelatedApproval): AuditPresentation;
23
+ export {};
@@ -0,0 +1,275 @@
1
+ import { extractChatThreadRef } from './chat.utils.js';
2
+ function formatTimestamp(value) {
3
+ if (!value)
4
+ return 0;
5
+ const time = new Date(String(value)).getTime();
6
+ return Number.isFinite(time) ? time : 0;
7
+ }
8
+ function getAuditAuthKey(audit) {
9
+ if (audit.action !== 'runtime.auth_required' && audit.action !== 'runtime.auth_resolved')
10
+ return null;
11
+ const method = audit.toolName || null;
12
+ const entry = audit.entry || null;
13
+ if (!method && !entry)
14
+ return null;
15
+ return `${audit.session ?? ''}:${method ?? ''}:${entry ?? ''}`;
16
+ }
17
+ function buildRuntimeSessionDescription(audit, fallback) {
18
+ const tool = audit.toolName ? `工具 ${audit.toolName}` : null;
19
+ return tool ?? fallback;
20
+ }
21
+ function buildApprovalDecisionDescription(audit, relatedApproval, decision) {
22
+ const toolName = audit.toolName || relatedApproval?.toolName || null;
23
+ const risk = relatedApproval?.risk ? `${relatedApproval.risk} 风险` : null;
24
+ const reason = relatedApproval?.reason?.trim() || null;
25
+ const lead = decision === 'approved' ? '收件箱已批准工具执行。' : '收件箱已拒绝工具执行。';
26
+ const parts = [toolName, risk, reason].filter(Boolean);
27
+ return parts.length > 0 ? `${lead} ${parts.join(' · ')}` : lead;
28
+ }
29
+ export function buildAuditDetailRecord(audit, relatedApproval) {
30
+ return {
31
+ action: audit.action,
32
+ actor: audit.actor,
33
+ actorRole: audit.actorRole,
34
+ onBehalfOf: audit.onBehalfOf || undefined,
35
+ session: audit.session || undefined,
36
+ entry: audit.entry || undefined,
37
+ toolCallId: audit.toolCallId || undefined,
38
+ toolName: audit.toolName || undefined,
39
+ approval: audit.approval || undefined,
40
+ policy: audit.policy || undefined,
41
+ policyVersion: audit.policyVersion || undefined,
42
+ createdAt: audit.createdAt,
43
+ relatedApproval: relatedApproval
44
+ ? {
45
+ target: relatedApproval.target,
46
+ toolName: relatedApproval.toolName,
47
+ risk: relatedApproval.risk,
48
+ status: relatedApproval.status,
49
+ reason: relatedApproval.reason || undefined,
50
+ }
51
+ : undefined,
52
+ };
53
+ }
54
+ export function formatInboxStatusLabel(status) {
55
+ if (!status)
56
+ return null;
57
+ switch (status) {
58
+ case 'pending':
59
+ return '待处理';
60
+ case 'resolved':
61
+ return '已完成';
62
+ case 'approved':
63
+ return '已批准';
64
+ case 'rejected':
65
+ return '已拒绝';
66
+ case 'active':
67
+ return '运行中';
68
+ case 'paused':
69
+ return '已暂停';
70
+ case 'completed':
71
+ return '已完成';
72
+ case 'error':
73
+ return '异常';
74
+ default:
75
+ return status;
76
+ }
77
+ }
78
+ export function formatAuditActorRole(role) {
79
+ switch (role) {
80
+ case 'system':
81
+ return '系统';
82
+ case 'human':
83
+ return '人工';
84
+ case 'secretary':
85
+ return '秘书';
86
+ default:
87
+ return role || '—';
88
+ }
89
+ }
90
+ export function createResolvedAuthTimestampsIndex(audits) {
91
+ const resolvedAuthTimestampsByKey = new Map();
92
+ for (const audit of audits) {
93
+ if (audit.action !== 'runtime.auth_resolved')
94
+ continue;
95
+ const authKey = getAuditAuthKey(audit);
96
+ if (!authKey)
97
+ continue;
98
+ const timestamps = resolvedAuthTimestampsByKey.get(authKey) ?? [];
99
+ timestamps.push(formatTimestamp(audit.createdAt));
100
+ resolvedAuthTimestampsByKey.set(authKey, timestamps);
101
+ }
102
+ return resolvedAuthTimestampsByKey;
103
+ }
104
+ export function buildAuditPresentation(audit, resolvedAuthTimestampsByKey, relatedApproval) {
105
+ const thread = audit.entry || relatedApproval?.target || null;
106
+ const about = relatedApproval?.target ?? audit.entry ?? audit.approval ?? null;
107
+ const { chatId, threadId } = extractChatThreadRef(thread);
108
+ const actorRoleLabel = formatAuditActorRole(audit.actorRole);
109
+ if (audit.action === 'runtime.auth_required') {
110
+ const method = audit.toolName || null;
111
+ const authKey = getAuditAuthKey(audit);
112
+ const createdAtTs = formatTimestamp(audit.createdAt);
113
+ const resolvedAtTs = authKey ? (resolvedAuthTimestampsByKey.get(authKey) ?? []) : [];
114
+ const isResolved = resolvedAtTs.some((value) => value >= createdAtTs);
115
+ return {
116
+ title: method ? `认证请求 · ${method}` : '认证请求',
117
+ description: method ? `运行时需要完成 ${method} 认证后才能继续。` : '运行时需要额外认证后才能继续。',
118
+ category: 'auth_required',
119
+ status: isResolved ? 'resolved' : 'pending',
120
+ chatId,
121
+ threadId,
122
+ thread,
123
+ about,
124
+ authUrl: null,
125
+ authMethod: method,
126
+ authMessage: null,
127
+ actorRoleLabel,
128
+ };
129
+ }
130
+ if (audit.action === 'runtime.auth_resolved') {
131
+ const method = audit.toolName || null;
132
+ return {
133
+ title: method ? `认证完成 · ${method}` : '认证完成',
134
+ description: '运行时认证已完成。',
135
+ category: 'audit',
136
+ status: 'resolved',
137
+ chatId,
138
+ threadId,
139
+ thread,
140
+ about,
141
+ authUrl: null,
142
+ authMethod: method,
143
+ authMessage: null,
144
+ actorRoleLabel,
145
+ };
146
+ }
147
+ if (audit.action === 'runtime.tool_call.waiting_approval') {
148
+ const toolName = audit.toolName || relatedApproval?.toolName || null;
149
+ const risk = relatedApproval?.risk ? `${relatedApproval.risk} 风险` : null;
150
+ return {
151
+ title: toolName ? `工具请求 · ${toolName}` : '工具请求',
152
+ description: [risk, '已进入审批队列'].filter(Boolean).join(' · ') || '工具调用已进入审批队列。',
153
+ category: 'audit',
154
+ status: undefined,
155
+ chatId,
156
+ threadId,
157
+ thread,
158
+ about,
159
+ authUrl: null,
160
+ authMethod: null,
161
+ authMessage: null,
162
+ actorRoleLabel,
163
+ };
164
+ }
165
+ if (audit.action === 'inbox.approval.approved') {
166
+ return {
167
+ title: '授权已批准',
168
+ description: buildApprovalDecisionDescription(audit, relatedApproval, 'approved'),
169
+ category: 'audit',
170
+ status: 'approved',
171
+ chatId,
172
+ threadId,
173
+ thread,
174
+ about,
175
+ authUrl: null,
176
+ authMethod: null,
177
+ authMessage: null,
178
+ actorRoleLabel,
179
+ };
180
+ }
181
+ if (audit.action === 'inbox.approval.rejected') {
182
+ return {
183
+ title: '授权已拒绝',
184
+ description: buildApprovalDecisionDescription(audit, relatedApproval, 'rejected'),
185
+ category: 'audit',
186
+ status: 'rejected',
187
+ chatId,
188
+ threadId,
189
+ thread,
190
+ about,
191
+ authUrl: null,
192
+ authMethod: null,
193
+ authMessage: null,
194
+ actorRoleLabel,
195
+ };
196
+ }
197
+ if (audit.action === 'runtime.session.active') {
198
+ return {
199
+ title: '运行时已启动',
200
+ description: buildRuntimeSessionDescription(audit, '运行时会话开始执行。'),
201
+ category: 'audit',
202
+ status: 'active',
203
+ chatId,
204
+ threadId,
205
+ thread,
206
+ about,
207
+ authUrl: null,
208
+ authMethod: null,
209
+ authMessage: null,
210
+ actorRoleLabel,
211
+ };
212
+ }
213
+ if (audit.action === 'runtime.session.paused') {
214
+ return {
215
+ title: '运行时已暂停',
216
+ description: buildRuntimeSessionDescription(audit, '运行时会话已暂停。'),
217
+ category: 'audit',
218
+ status: 'paused',
219
+ chatId,
220
+ threadId,
221
+ thread,
222
+ about,
223
+ authUrl: null,
224
+ authMethod: null,
225
+ authMessage: null,
226
+ actorRoleLabel,
227
+ };
228
+ }
229
+ if (audit.action === 'runtime.session.completed') {
230
+ return {
231
+ title: '运行时已完成',
232
+ description: buildRuntimeSessionDescription(audit, '运行时会话已完成。'),
233
+ category: 'audit',
234
+ status: 'completed',
235
+ chatId,
236
+ threadId,
237
+ thread,
238
+ about,
239
+ authUrl: null,
240
+ authMethod: null,
241
+ authMessage: null,
242
+ actorRoleLabel,
243
+ };
244
+ }
245
+ if (audit.action === 'runtime.session.error') {
246
+ return {
247
+ title: '运行时异常',
248
+ description: buildRuntimeSessionDescription(audit, '运行时会话执行失败。'),
249
+ category: 'audit',
250
+ status: 'error',
251
+ chatId,
252
+ threadId,
253
+ thread,
254
+ about,
255
+ authUrl: null,
256
+ authMethod: null,
257
+ authMessage: null,
258
+ actorRoleLabel,
259
+ };
260
+ }
261
+ return {
262
+ title: audit.action,
263
+ description: actorRoleLabel,
264
+ category: 'audit',
265
+ status: undefined,
266
+ chatId,
267
+ threadId,
268
+ thread,
269
+ about,
270
+ authUrl: null,
271
+ authMethod: null,
272
+ authMessage: null,
273
+ actorRoleLabel,
274
+ };
275
+ }