@fg-lab/amadeus-qq 1.2.1 → 1.2.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 (43) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +55 -53
  3. package/README.zh-CN.md +51 -51
  4. package/REFERENCE.md +49 -49
  5. package/cordis.patch.yml +6 -6
  6. package/docs/DATA_MIGRATION.md +26 -26
  7. package/docs/INSTALL_V1_2.md +22 -22
  8. package/docs/PUBLISHING.md +28 -26
  9. package/docs/RELEASE_CHECKLIST.md +23 -23
  10. package/docs/RELEASE_NOTES.md +38 -38
  11. package/docs/UPGRADE_POLICY.md +26 -26
  12. package/docs/verification/IMAGE_READ_FIX_2026-11-07.md +22 -0
  13. package/docs/verification/LIFE_QQ_BOUNDARY_2026-11-07.md +20 -0
  14. package/lib/client.js +372 -372
  15. package/package.json +94 -91
  16. package/preset.patch.yml +63 -0
  17. package/scripts/symlink-security.mjs +1 -1
  18. package/src/account-profile.js +223 -223
  19. package/src/admin-http.js +361 -361
  20. package/src/audio.js +428 -428
  21. package/src/capabilities.js +785 -785
  22. package/src/client/factory.template.js +82 -82
  23. package/src/client/logic.js +290 -290
  24. package/src/config.js +2 -0
  25. package/src/console/console.css +28 -28
  26. package/src/console/console.js +3 -0
  27. package/src/console/index.html +67 -67
  28. package/src/core.js +21 -4
  29. package/src/files.js +230 -230
  30. package/src/index.js +135 -8
  31. package/src/lease.js +42 -42
  32. package/src/media.js +66 -3
  33. package/src/napcat-action-names.js +186 -186
  34. package/src/native-asr.js +112 -112
  35. package/src/onebot.js +378 -378
  36. package/src/outbound-media.js +309 -309
  37. package/src/owner-workspace.js +394 -394
  38. package/src/permissions.js +20 -0
  39. package/src/preset-guard.mjs +16 -2
  40. package/src/qzone-provider.js +628 -628
  41. package/src/session-provenance.js +74 -74
  42. package/src/setup.js +255 -255
  43. package/src/web-console.js +9 -9
@@ -59,6 +59,9 @@ export const CAPABILITY_CATALOG = Object.freeze([
59
59
  Object.freeze({ id: 'qq.media.read', implemented: true }),
60
60
  Object.freeze({ id: 'qq.owner.workspace', implemented: true }),
61
61
  Object.freeze({ id: 'qq.owner.tools', implemented: true }),
62
+ // Explicitly grants only the session-scoped Life read tools. This is not an
63
+ // Owner tool grant and never exposes Life admin/intent mutation surfaces.
64
+ Object.freeze({ id: 'amadeus.life.read', implemented: true }),
62
65
  Object.freeze({ id: 'qq.reaction.write', implemented: true }),
63
66
  Object.freeze({ id: 'qq.poke', implemented: true }),
64
67
  Object.freeze({ id: 'qq.recall', implemented: true }),
@@ -77,6 +80,8 @@ export const CAPABILITY_CATALOG = Object.freeze([
77
80
  Object.freeze({ id: 'qq.qzone.publish', implemented: true }),
78
81
  Object.freeze({ id: 'dsh.shell', implemented: false }),
79
82
  ])
83
+ export const LIFE_READ_CAPABILITY = 'amadeus.life.read'
84
+ export const LIFE_READ_TOOLS = Object.freeze(['life_shared_read', 'life_health'])
80
85
  const KNOWN_CAPABILITIES = new Set(CAPABILITY_CATALOG.map(item => item.id))
81
86
  const DIRECTORY_CAPABILITIES = new Set(['qq.friends.read', 'qq.groups.read'])
82
87
  const QZONE_CAPABILITIES = new Set(['qq.qzone.use', 'qq.qzone.read', 'qq.qzone.publish'])
@@ -143,6 +148,7 @@ export class PermissionEngine {
143
148
  throw new TypeError('groupDefaultMode cannot be owner-agent: groups cannot be Owner Agent Scenes')
144
149
  }
145
150
  this.ownerPrivateIds = idSet(raw.ownerPrivateIds ?? [], 'ownerPrivateIds')
151
+ this.lifeReadPrivateIds = idSet(raw.lifeReadPrivateIds ?? [], 'lifeReadPrivateIds')
146
152
  this.ownerTools = stringSet(raw.ownerTools ?? [], 'ownerTools')
147
153
  this.globalDeny = stringSet(raw.globalDeny ?? [], 'globalDeny')
148
154
  this.accountGrants = raw.accountGrants ?? {}
@@ -197,6 +203,19 @@ export class PermissionEngine {
197
203
  }
198
204
  return decision(true, 'owner-agent')
199
205
  }
206
+ if (capability === LIFE_READ_CAPABILITY) {
207
+ // Life tools are session-scoped and read-only, but still require an
208
+ // explicit Scene grant so ordinary QQ chat cannot discover them by
209
+ // default. A configured private allowlist is an equivalent explicit
210
+ // authorization for this narrow read-only capability. The Life module
211
+ // performs the final binding/ACL check.
212
+ if (scene.kind === 'private' && this.lifeReadPrivateIds.has(scene.peerId) && override !== 'deny') {
213
+ return decision(true, 'configured-life-read-private')
214
+ }
215
+ return override === 'allow'
216
+ ? decision(true, 'scene-allow')
217
+ : decision(false, override === 'deny' ? 'scene-deny' : 'life-read-explicit')
218
+ }
200
219
  if (capability === 'qq.owner.workspace') {
201
220
  // The file surface is never implied by owner-agent mode and is never
202
221
  // reachable from Safe Chat; the Owner must explicitly allow this Scene.
@@ -248,3 +267,4 @@ export class PermissionEngine {
248
267
  return decision(MODE_TO_CAPABILITIES[mode].includes(capability), 'mode')
249
268
  }
250
269
  }
270
+
@@ -49,6 +49,10 @@ export const OWNER_NATIVE_TOOLS = Object.freeze([
49
49
  */
50
50
  export const MEMORY_READ_TOOLS = Object.freeze(['memory_search', 'memory_list', 'memory_get'])
51
51
  const MEMORY_READ_TOOL_SET = new Set(MEMORY_READ_TOOLS)
52
+ // Session-bound Life views are safe to expose by name; the connector applies
53
+ // the explicit Scene capability at execution time.
54
+ export const LIFE_READ_TOOLS = Object.freeze(['life_shared_read', 'life_health'])
55
+ const LIFE_READ_TOOL_SET = new Set(LIFE_READ_TOOLS)
52
56
 
53
57
  /**
54
58
  * Cordis service contract for the trusted-Scene read capability.
@@ -183,6 +187,8 @@ export function resolveQQPermissionPolicy(config = {}) {
183
187
  }
184
188
  const allowSearch = config.allowSearch === undefined ? false : config.allowSearch
185
189
  if (typeof allowSearch !== 'boolean') throw new TypeError('QQ preset allowSearch must be a boolean')
190
+ const allowLifeRead = config.allowLifeRead === undefined ? false : config.allowLifeRead
191
+ if (typeof allowLifeRead !== 'boolean') throw new TypeError('QQ preset allowLifeRead must be a boolean')
186
192
  const entry = QQ_PERMISSION_LEVELS[level]
187
193
  // Memory readers are intentionally not part of the static preset policy.
188
194
  // They are opened only after the live Memory service declares the trusted
@@ -191,7 +197,7 @@ export function resolveQQPermissionPolicy(config = {}) {
191
197
  const tools = allowSearch
192
198
  ? [...toolsForLevel(level), 'web_search']
193
199
  : toolsForLevel(level)
194
- return Object.freeze({ level, levelId: entry.id, tools: Object.freeze(tools), allowSearch })
200
+ return Object.freeze({ level, levelId: entry.id, tools: Object.freeze(tools), allowSearch, allowLifeRead })
195
201
  }
196
202
 
197
203
  /**
@@ -211,7 +217,7 @@ export function apply(ctx, config = {}) {
211
217
  // policy only when the trusted-Scene contract is present.
212
218
  const OPTIONAL_TOOLS = new Set([
213
219
  'qq_send_media', 'qq_upload_file', 'qq_qzone', 'qq_account_profile', OWNER_WORKSPACE_TOOL,
214
- ...MEMORY_READ_TOOLS,
220
+ ...MEMORY_READ_TOOLS, ...LIFE_READ_TOOLS,
215
221
  ])
216
222
  const OWNER_NATIVE = new Set(OWNER_NATIVE_TOOLS)
217
223
  // Host plugins can register tools after this preset starts. Keep the QQ
@@ -403,6 +409,11 @@ export function apply(ctx, config = {}) {
403
409
  ? undefined
404
410
  : 'QQ Memory trusted Scene ACL read contract is unavailable'
405
411
  }
412
+ if (LIFE_READ_TOOL_SET.has(tool)) {
413
+ return authorizeCapability(exec, 'amadeus.life.read')
414
+ ? undefined
415
+ : 'QQ Session Life read permission denied'
416
+ }
406
417
  if (allowed.has(tool)) return undefined
407
418
  if (Object.hasOwn(QQ_TOOL_CONTRACT, tool)) {
408
419
  return `QQ Session permission level ${policy.level} does not allow ${tool}`
@@ -439,6 +450,7 @@ export function apply(ctx, config = {}) {
439
450
  const fresh = schemas
440
451
  .map(schema => schema?.name)
441
452
  .filter(name => typeof name === 'string' && !MEMORY_READ_TOOL_SET.has(name) &&
453
+ !LIFE_READ_TOOL_SET.has(name) &&
442
454
  !allowed.has(name) &&
443
455
  !(policy.level === 4 && ownerHostTool(name)) && name !== 'run_code' && !deniedVisible.has(name))
444
456
  if (fresh.length === 0) return
@@ -479,6 +491,7 @@ export function apply(ctx, config = {}) {
479
491
  const visiblePolicy = [
480
492
  ...policy.tools,
481
493
  ...(memoryReadGranted ? MEMORY_READ_TOOLS : []),
494
+ ...(policy.allowLifeRead ? LIFE_READ_TOOLS : []),
482
495
  ...(policy.level === 4
483
496
  ? [OWNER_WORKSPACE_TOOL, ...[...known].filter(ownerHostTool)]
484
497
  : []),
@@ -577,3 +590,4 @@ export function apply(ctx, config = {}) {
577
590
  }))
578
591
  }
579
592
  }
593
+