smoke.spec.ts 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  1. import { describe, expect, it } from 'vitest';
  2. import { apply, currentWorkspaceRoot, name, registerWorkspaceRoot, resetWorkspaceRoot } from '../src/index';
  3. import * as fs from 'node:fs'
  4. import * as os from 'node:os'
  5. import * as path from 'node:path'
  6. function makeWorkspace(): string {
  7. const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'webnovel-bundle-'))
  8. const book = path.join(dir, '星辰')
  9. fs.mkdirSync(path.join(book, '作品契约'), { recursive: true })
  10. fs.writeFileSync(path.join(book, '作品契约', '契约.md'), '---\n书id: xingchen-001\n---\n正文\n', 'utf8')
  11. return dir
  12. }
  13. describe('包冒烟', () => {
  14. it('可导入', () => {
  15. expect(name).toBe('webnovel-bundle');
  16. });
  17. it('无 tools 表面仍加载', () => {
  18. const logs: string[] = []
  19. expect(() => apply({
  20. logger: { info: (m: string) => { logs.push(m) }, warn: (m: string) => { logs.push(m) } },
  21. } as never)).not.toThrow()
  22. expect(logs.some((m) => m.includes('工作台插件已加载'))).toBe(true)
  23. });
  24. it('接线:agent/created 触发 status context 注册与文件门禁挂载', () => {
  25. const ws = makeWorkspace()
  26. registerWorkspaceRoot(ws)
  27. const createdListeners: Array<(p: { agent: AgentLike }) => void> = []
  28. const agentCtxEvents: string[] = []
  29. const registeredContexts: Array<{ name: string; order: number; text: (a: unknown) => string }> = []
  30. interface AgentLike { id: string; ctx: FakeAgentCtx }
  31. interface FakeAgentCtx {
  32. systemPrompt: { context: (c: unknown) => void }
  33. inject: (deps: unknown, cb: (scope: unknown) => void) => unknown
  34. on: (e: string) => void
  35. }
  36. const makeAgentCtx = (): FakeAgentCtx => ({
  37. systemPrompt: {
  38. context: (c) => { registeredContexts.push(c as never) },
  39. },
  40. inject: (_deps, cb) => {
  41. cb(fakeCtx)
  42. return undefined
  43. },
  44. on: (e) => { agentCtxEvents.push(e) },
  45. })
  46. const fakeCtx = makeAgentCtx()
  47. const hostCtx = {
  48. logger: { info: () => {}, warn: () => {} },
  49. get: (n: string) => {
  50. if (n === 'workspaceRegistry') {
  51. return { list: () => [{ path: ws }] }
  52. }
  53. if (n === 'agents') return { list: () => [{ id: 'sess-1', ctx: fakeCtx }] }
  54. return undefined
  55. },
  56. on: (e: string, l: unknown) => { if (e === 'agent/created') createdListeners.push(l as never) },
  57. }
  58. // apply 会注册 agent/created 监听器并初装现有 agents
  59. apply(hostCtx as never)
  60. // 初装:现有 sess-1 已 attach
  61. expect(registeredContexts.length).toBeGreaterThanOrEqual(1)
  62. const status = registeredContexts.find((c) => c.name === 'webnovel.status')
  63. expect(status).toBeDefined()
  64. expect(status!.text({})).toContain('【工作区总览】')
  65. expect(status!.text({})).toContain('《星辰》')
  66. // agent/created 新起 agent 也 attach
  67. const newCtx = makeAgentCtx()
  68. for (const l of createdListeners) l({ agent: { id: 'sess-2', ctx: newCtx } })
  69. const created = registeredContexts.filter((c) => c.name === 'webnovel.status')
  70. expect(created.length).toBe(2)
  71. });
  72. it('headless 兜底:无 workspaceRegistry 服务时退回进程 cwd,总览不静默缺席', () => {
  73. // 工作范围是模块级单例,跨用例存活(审计 B5):经显式重置入口清掉前序用例的认领残留
  74. resetWorkspaceRoot()
  75. // 真机实证(2026-09-01):workspaceRegistry 只被 dsh-host-apiproxy / dsh-web-app 依赖,
  76. // headless profile 不装它 —— inject 的 setup 永不执行。此前该情形下工作范围认不到,
  77. // 状态注入返回空串被组装侧 filter(text.length > 0) 丢弃,总览静默缺席且无任何报错。
  78. const registered: Array<{ name: string; text: unknown }> = []
  79. const fakeCtx = {
  80. systemPrompt: {
  81. context: (c: unknown) => { registered.push(c as never) },
  82. variable: () => {},
  83. },
  84. inject: (_d: unknown, cb: (s: unknown) => void) => { cb(fakeCtx); return undefined },
  85. on: () => {},
  86. }
  87. const hostCtx = {
  88. logger: { info: () => {}, warn: () => {} },
  89. // headless:workspaceRegistry 缺席
  90. get: (n: string) => (n === 'agents' ? { list: () => [{ id: 'h-1', ctx: fakeCtx }] } : undefined),
  91. inject: (_d: unknown, cb: (s: unknown) => void) => { cb(hostCtx); return undefined },
  92. on: () => {},
  93. }
  94. apply(hostCtx as never)
  95. expect(currentWorkspaceRoot()).toBe(process.cwd())
  96. });
  97. });