inventory.spec.ts 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
  1. import { afterEach, describe, expect, it } from 'vitest'
  2. import { Context, FiberState, type Plugin } from '@deepseek-ai/cordis'
  3. import Loader from '@deepseek-ai/cordis-plugin-loader'
  4. import { remoteMethods } from '@deepseek-ai/dsh-typert-protocol'
  5. import type { AgentPresets } from '@deepseek-ai/dsh-agent-presets'
  6. import PluginInventoryGateway from '../src/index.ts'
  7. const contexts: Context[] = []
  8. afterEach(async () => {
  9. await Promise.all(contexts.splice(0).map(ctx => ctx.fiber.dispose()))
  10. })
  11. const activePlugin: Plugin.Function = () => {}
  12. const pendingPlugin: Plugin.Object = {
  13. inject: ['neverReady'],
  14. apply() {},
  15. }
  16. async function harness(): Promise<{
  17. ctx: Context
  18. inventory: PluginInventoryGateway
  19. }> {
  20. const ctx = new Context()
  21. contexts.push(ctx)
  22. await ctx.plugin(Loader)
  23. ctx.loader.builtins.active = activePlugin
  24. ctx.loader.builtins.pending = pendingPlugin
  25. await ctx.plugin(PluginInventoryGateway)
  26. const inventory = ctx.get('pluginInventory') as PluginInventoryGateway
  27. return { ctx, inventory }
  28. }
  29. describe('PluginInventoryGateway', () => {
  30. it('publishes one direct list method under the pluginInventory namespace', async () => {
  31. const { inventory } = await harness()
  32. expect(inventory.typertRemote).toMatchObject({
  33. serviceKey: 'pluginInventory',
  34. namespace: 'pluginInventory',
  35. })
  36. expect(remoteMethods(inventory)).toEqual([
  37. { method: 'list', invocation: { kind: 'direct' } },
  38. ])
  39. })
  40. it('projects current non-group Loader entries without a second cache', async () => {
  41. const { ctx, inventory } = await harness()
  42. const activeId = await ctx.loader.create({ name: 'cordis:active' })
  43. const pendingId = await ctx.loader.create({ name: 'cordis:pending' })
  44. const disabledId = await ctx.loader.create({
  45. name: 'cordis:not-installed',
  46. disabled: true,
  47. })
  48. await ctx.loader.create({ name: 'cordis:active', group: true })
  49. const snapshot = await inventory.list()
  50. // No agent-preset roster is composed, so the snapshot carries no presets.
  51. expect(snapshot.agentPresets).toBeUndefined()
  52. expect(snapshot.entries).toHaveLength(3)
  53. expect(snapshot.entries).toEqual(expect.arrayContaining([
  54. {
  55. entryId: activeId,
  56. moduleName: 'cordis:active',
  57. enabled: true,
  58. fiberPhase: 'active',
  59. },
  60. {
  61. entryId: pendingId,
  62. moduleName: 'cordis:pending',
  63. enabled: true,
  64. fiberPhase: 'pending',
  65. },
  66. {
  67. entryId: disabledId,
  68. moduleName: 'cordis:not-installed',
  69. enabled: false,
  70. fiberPhase: null,
  71. },
  72. ]))
  73. await ctx.loader.update(activeId, { disabled: true })
  74. expect((await inventory.list()).entries.find(entry => entry.entryId === activeId)).toEqual({
  75. entryId: activeId,
  76. moduleName: 'cordis:active',
  77. enabled: false,
  78. fiberPhase: null,
  79. })
  80. await ctx.loader.remove(pendingId)
  81. expect((await inventory.list()).entries.some(entry => entry.entryId === pendingId)).toBe(false)
  82. })
  83. it('carries each composed preset with root-fiber states mapped to phases', async () => {
  84. const { ctx, inventory } = await harness()
  85. ctx.provide('agentPresets', {
  86. compositionInventory: async () => [
  87. {
  88. id: 'standard',
  89. trust: 'system',
  90. name: '标准模式',
  91. isDefault: true,
  92. rows: [
  93. { entryId: 'alpha', moduleName: 'pkg-alpha', enabled: true, fiberState: FiberState.ACTIVE },
  94. { entryId: null, moduleName: 'pkg-file', enabled: 'conditional', condition: 'x' },
  95. ],
  96. },
  97. { id: 'damaged', trust: 'user', isDefault: false, broken: 'the composition file is missing', rows: [] },
  98. ],
  99. } as Partial<AgentPresets> as never)
  100. const snapshot = await inventory.list()
  101. expect(snapshot.agentPresets).toEqual([
  102. {
  103. id: 'standard',
  104. trust: 'system',
  105. name: '标准模式',
  106. isDefault: true,
  107. rows: [
  108. { entryId: 'alpha', moduleName: 'pkg-alpha', enabled: true, fiberPhase: 'active' },
  109. { entryId: null, moduleName: 'pkg-file', enabled: 'conditional', condition: 'x', fiberPhase: null },
  110. ],
  111. },
  112. { id: 'damaged', trust: 'user', isDefault: false, broken: 'the composition file is missing', rows: [] },
  113. ])
  114. })
  115. })