This commit is contained in:
@@ -888,3 +888,112 @@ describe('ReadUserAgents / UpdateUserAgents', () => {
|
||||
expect(TOOL_DEFS['UpdateUserAgents']).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
// ── spaces foundation: AGENTS.md / memory は folderContext を尊重する ──────────
|
||||
// バグ回帰: ケーススペースで作業中、AGENTS.md / memory が個人ユーザーフォルダ
|
||||
// (userFolderRoot/userId)に書かれてしまい、スペースの AGENTS.md を編集できなかった。
|
||||
// folderContext が在れば (rootDir, leafId) を使い、無ければ従来の個人フォルダ。
|
||||
describe('user-folder tools honor ctx.folderContext (space resolution)', () => {
|
||||
let spacesRoot: string;
|
||||
const SPACE_ID = 'space-xyz';
|
||||
|
||||
beforeEach(() => {
|
||||
spacesRoot = join(tempDir, 'spaces');
|
||||
mkdirSync(join(spacesRoot, SPACE_ID, 'memory'), { recursive: true });
|
||||
mkdirSync(join(spacesRoot, SPACE_ID, 'trash'), { recursive: true });
|
||||
});
|
||||
|
||||
const spaceCtx = () =>
|
||||
buildCtx({ userId: TEST_USER, folderContext: { rootDir: spacesRoot, leafId: SPACE_ID } });
|
||||
|
||||
it('UpdateUserAgents writes to the SPACE folder, not the personal user folder', async () => {
|
||||
const res = await executeTool(
|
||||
'UpdateUserAgents',
|
||||
{ mode: 'append', new_text: '# Space rules\nuse the space brief' },
|
||||
spaceCtx(),
|
||||
);
|
||||
expect(res.isError).toBe(false);
|
||||
// Written under spaces/<id>/AGENTS.md ...
|
||||
const spaceAgents = join(spacesRoot, SPACE_ID, 'AGENTS.md');
|
||||
expect(readFileSync(spaceAgents, 'utf-8')).toContain('use the space brief');
|
||||
// ... and NOT under the personal user folder.
|
||||
expect(() => readFileSync(join(userFolderRoot, TEST_USER, 'AGENTS.md'), 'utf-8')).toThrow();
|
||||
});
|
||||
|
||||
it('ReadUserAgents reads the SPACE AGENTS.md', async () => {
|
||||
writeFileSync(join(spacesRoot, SPACE_ID, 'AGENTS.md'), 'SPACE-ONLY-MARKER');
|
||||
// personal one would mislead if resolution were wrong
|
||||
writeFileSync(join(userFolderRoot, TEST_USER, 'AGENTS.md'), 'PERSONAL-MARKER');
|
||||
const res = await executeTool('ReadUserAgents', {}, spaceCtx());
|
||||
expect(res.output).toContain('SPACE-ONLY-MARKER');
|
||||
expect(res.output).not.toContain('PERSONAL-MARKER');
|
||||
});
|
||||
|
||||
it('UpdateUserMemory / ReadUserMemory target the SPACE memory dir', async () => {
|
||||
const up = await executeTool(
|
||||
'UpdateUserMemory',
|
||||
{ action: 'upsert', name: 'sp-note', type: 'project', description: 'd', body: 'space body' },
|
||||
spaceCtx(),
|
||||
);
|
||||
expect(up.isError).toBe(false);
|
||||
expect(readFileSync(join(spacesRoot, SPACE_ID, 'memory', 'sp-note.md'), 'utf-8')).toContain('space body');
|
||||
// Not in the personal memory dir.
|
||||
expect(() => readFileSync(join(userFolderRoot, TEST_USER, 'memory', 'sp-note.md'), 'utf-8')).toThrow();
|
||||
// And readable back through the space context.
|
||||
const rd = await executeTool('ReadUserMemory', { name: 'sp-note' }, spaceCtx());
|
||||
expect(rd.isError).toBe(false);
|
||||
expect(rd.output).toContain('space body');
|
||||
});
|
||||
|
||||
it('without folderContext, still writes to the personal user folder (legacy)', async () => {
|
||||
const res = await executeTool(
|
||||
'UpdateUserAgents',
|
||||
{ mode: 'append', new_text: 'personal rule' },
|
||||
buildCtx({ userId: TEST_USER }),
|
||||
);
|
||||
expect(res.isError).toBe(false);
|
||||
expect(readFileSync(join(userFolderRoot, TEST_USER, 'AGENTS.md'), 'utf-8')).toContain('personal rule');
|
||||
});
|
||||
|
||||
// ── workstream 2: browser-macros / recordings honor folderContext ──────────
|
||||
it('WriteUserScript writes the macro into the SPACE browser-macros/, not personal', async () => {
|
||||
mkdirSync(join(spacesRoot, SPACE_ID, 'browser-macros'), { recursive: true });
|
||||
const res = await executeTool(
|
||||
'WriteUserScript',
|
||||
{ name: 'space-macro', content: 'async function main() { return 1; }' },
|
||||
spaceCtx(),
|
||||
);
|
||||
expect(res.isError).toBe(false);
|
||||
expect(readFileSync(join(spacesRoot, SPACE_ID, 'browser-macros', 'space-macro.js'), 'utf-8')).toContain('main');
|
||||
// NOT in the personal folder.
|
||||
expect(() => readFileSync(join(userFolderRoot, TEST_USER, 'browser-macros', 'space-macro.js'), 'utf-8')).toThrow();
|
||||
});
|
||||
|
||||
it('RunUserScript resolves the macro from the SPACE folder', async () => {
|
||||
mkdirSync(join(spacesRoot, SPACE_ID, 'browser-macros'), { recursive: true });
|
||||
writeFileSync(join(spacesRoot, SPACE_ID, 'browser-macros', 'sp-run.js'), SCRIPT_NO_FM);
|
||||
const res = await executeTool('RunUserScript', { name: 'sp-run' }, spaceCtx());
|
||||
expect(res.isError).toBe(false);
|
||||
expect(res.output).toContain('42');
|
||||
});
|
||||
|
||||
it('ListUserAssets lists the SPACE browser-macros, not the personal ones', async () => {
|
||||
mkdirSync(join(spacesRoot, SPACE_ID, 'browser-macros'), { recursive: true });
|
||||
writeFileSync(join(spacesRoot, SPACE_ID, 'browser-macros', 'sp-macro.js'), SCRIPT_BAR);
|
||||
writeFileSync(join(userFolderRoot, TEST_USER, 'browser-macros', 'personal-macro.js'), SCRIPT_FOO);
|
||||
const res = await executeTool('ListUserAssets', { kind: 'browser-macros' }, spaceCtx());
|
||||
expect(res.isError).toBe(false);
|
||||
expect(res.output).toContain('sp-macro.js');
|
||||
expect(res.output).not.toContain('personal-macro.js');
|
||||
});
|
||||
|
||||
it('without folderContext, WriteUserScript still targets the personal folder', async () => {
|
||||
const res = await executeTool(
|
||||
'WriteUserScript',
|
||||
{ name: 'personal-macro', content: 'async function main() { return 2; }' },
|
||||
buildCtx({ userId: TEST_USER }),
|
||||
);
|
||||
expect(res.isError).toBe(false);
|
||||
expect(readFileSync(join(userFolderRoot, TEST_USER, 'browser-macros', 'personal-macro.js'), 'utf-8')).toContain('main');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user