Browse Source

test(tools): prove persistent tool disposal

Tianyi Cui 1 month ago
parent
commit
9939236dcb

+ 7 - 3
packages/fs/tool-str-replace-editor/tests/tools.spec.ts

@@ -78,13 +78,13 @@ async function setup(
     await ctx.plugin(SandboxedFileSystem, { cwd: root })
     await ctx.plugin(SandboxedFileSystem, { cwd: root })
   }
   }
   if (options.fsPolicy === true) await ctx.plugin(FsPolicy)
   if (options.fsPolicy === true) await ctx.plugin(FsPolicy)
-  await ctx.plugin(ToolStrReplaceEditor, config)
-  return { ctx, root, owner: agent(ctx, root) }
+  const fiber = await ctx.plugin(ToolStrReplaceEditor, config)
+  return { ctx, root, fiber, owner: agent(ctx, root) }
 }
 }
 
 
 describe('tool-str-replace-editor', () => {
 describe('tool-str-replace-editor', () => {
   it('registers the standalone schema and configurable description', async () => {
   it('registers the standalone schema and configurable description', async () => {
-    const { ctx } = await setup({ description: 'custom editor description' })
+    const { ctx, fiber } = await setup({ description: 'custom editor description' })
     const schema = ctx.tools.schemas()[0]
     const schema = ctx.tools.schemas()[0]
     expect(ctx.tools.schemas().map(item => item.name)).toEqual(['str_replace_editor'])
     expect(ctx.tools.schemas().map(item => item.name)).toEqual(['str_replace_editor'])
     expect(schema?.description).toBe('custom editor description')
     expect(schema?.description).toBe('custom editor description')
@@ -147,6 +147,10 @@ describe('tool-str-replace-editor', () => {
     })).toMatchObject({
     })).toMatchObject({
       locations: [{ path: '/workspace/a.txt' }],
       locations: [{ path: '/workspace/a.txt' }],
     })
     })
+
+    await fiber.dispose()
+    expect(ctx.tools.schemas()).toEqual([])
+    expect(ctx.tools.get('str_replace_editor')).toBeUndefined()
   })
   })
 
 
   it('creates, views, replaces, and inserts with the canonical model-facing output', async () => {
   it('creates, views, replaces, and inserts with the canonical model-facing output', async () => {

+ 3 - 2
packages/pty/tool-bash-persistent/src/invariant.ts

@@ -15,8 +15,9 @@ export const name = 'tool-bash-persistent-invariant'
 export const inject = ['invariants']
 export const inject = ['invariants']
 
 
 /**
 /**
- * No runtime invariant: the tool adapter owns no independent durable state;
- * PTY ownership and filesystem mutation relations stay with their services.
+ * No runtime invariant: the adapter's private owner-to-shell cache has no
+ * observable event or data relation. Lifecycle tests prove its cleanup without
+ * adding a public surface solely for an invariant.
  */
  */
 const install: InvariantInstaller = () => {}
 const install: InvariantInstaller = () => {}
 
 

+ 5 - 1
packages/pty/tool-bash-persistent/tests/tools.spec.ts

@@ -260,7 +260,7 @@ async function setup(
 
 
 describe('tool-bash-persistent', () => {
 describe('tool-bash-persistent', () => {
   it('registers a configurable schema and reuses one owner shell', async () => {
   it('registers a configurable schema and reuses one owner shell', async () => {
-    const { ctx, owner, stub } = await setup({
+    const { ctx, owner, stub, fiber } = await setup({
       backendType: 'stub',
       backendType: 'stub',
       description: 'deployment-specific persistent shell',
       description: 'deployment-specific persistent shell',
     })
     })
@@ -282,6 +282,10 @@ describe('tool-bash-persistent', () => {
     const ownerWithoutCwd = agent(ctx, undefined)
     const ownerWithoutCwd = agent(ctx, undefined)
     expect(text(await call(ctx, ownerWithoutCwd, 'pwd'))).toBe('hello from stub')
     expect(text(await call(ctx, ownerWithoutCwd, 'pwd'))).toBe('hello from stub')
     expect(stub.sessions).toHaveLength(2)
     expect(stub.sessions).toHaveLength(2)
+
+    await fiber.dispose()
+    expect(ctx.tools.schemas()).toEqual([])
+    expect(ctx.tools.get('bash')).toBeUndefined()
   })
   })
 
 
   it('handles inferred idle, prompt fallback, shell exit, clipping, and cleanup', async () => {
   it('handles inferred idle, prompt fallback, shell exit, clipping, and cleanup', async () => {