Rebuilt from the standalone 2101cb7 tree (durable transactions, Kiro IDE adapter, hook retry with a bounded chain lock, version 2.0.1) with the Open-Source API trims re-applied: no webhooks, members, admin events, deep health, token rotation, or agent integration updates. Claude-Session: https://claude.ai/code/session_01QoUjdc7HGKTsgj5GhG8HvF
335 lines
14 KiB
JavaScript
335 lines
14 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import { lstat, readFile, writeFile } from 'node:fs/promises';
|
|
import path from 'node:path';
|
|
import test from 'node:test';
|
|
import {
|
|
VALID_PRIVATE_KEY,
|
|
cliPath,
|
|
createFixture,
|
|
environment,
|
|
managedHandler,
|
|
readSettings,
|
|
registryModuleUrl,
|
|
runNode,
|
|
runPlugin,
|
|
} from '../test-support/letta.mjs';
|
|
|
|
async function assertMissing(filePath) {
|
|
await assert.rejects(lstat(filePath), { code: 'ENOENT' });
|
|
}
|
|
|
|
function assertManagedTriple(settings) {
|
|
for (const event of ['PreToolUse', 'PostToolUse', 'PostToolUseFailure']) {
|
|
const group = settings.hooks[event].find((entry) => entry.hooks[0]?.timeout === 10_000);
|
|
assert.deepEqual(Object.keys(group).sort(), ['hooks', 'matcher']);
|
|
assert.equal(group.matcher, '*');
|
|
assert.equal(group.hooks.length, 1);
|
|
assert.deepEqual(Object.keys(group.hooks[0]).sort(), ['command', 'timeout', 'type']);
|
|
assert.equal(group.hooks[0].type, 'command');
|
|
assert.equal(group.hooks[0].timeout, 10_000);
|
|
assert.doesNotMatch(group.hooks[0].command, /^node /);
|
|
}
|
|
assert.equal(
|
|
managedHandler(settings, 'PostToolUse').command,
|
|
managedHandler(settings, 'PostToolUseFailure').command,
|
|
);
|
|
}
|
|
|
|
test('Letta Code is registered in the SDK and CLI', async () => {
|
|
const { SUPPORTED_AGENTS } = await import(registryModuleUrl);
|
|
assert.deepEqual(SUPPORTED_AGENTS.get('letta'), {
|
|
name: 'Letta Code',
|
|
configDir: '~/.letta',
|
|
configFile: 'settings.json',
|
|
});
|
|
const fixture = await createFixture();
|
|
try {
|
|
const result = await runNode(
|
|
['--no-warnings', cliPath, 'status'],
|
|
environment(fixture),
|
|
fixture.projectDir,
|
|
);
|
|
assert.equal(result.code, 0, result.stderr);
|
|
assert.match(result.stdout, /Letta Code \(letta\)/);
|
|
} finally {
|
|
await fixture.close();
|
|
}
|
|
});
|
|
|
|
test('Letta installs the current three-event contract and preserves every source', async () => {
|
|
const globalSettings = [
|
|
'{',
|
|
' "theme": "dark",',
|
|
' "hooks": {',
|
|
' "FutureEvent": [null],',
|
|
' "PreToolUse": [{ "matcher": "Bash", "hooks": [{ "type": "command", "command": "user-hook", "quiet": true }] }]',
|
|
' }',
|
|
'}',
|
|
'',
|
|
].join('\r\n');
|
|
const projectSettings = { hooks: { PostToolUse: [{
|
|
matcher: 'Read',
|
|
hooks: [{ type: 'prompt', prompt: 'Review $ARGUMENTS', model: 'fast' }],
|
|
}] } };
|
|
const localSettings = { windowTitle: 'workspace' };
|
|
const fixture = await createFixture({ globalSettings, projectSettings, localSettings });
|
|
try {
|
|
const projectBefore = await readFile(fixture.projectPath, 'utf-8');
|
|
const localBefore = await readFile(fixture.localPath, 'utf-8');
|
|
const first = await fixture.install();
|
|
assert.equal(first.code, 0, first.stderr);
|
|
assert.match(first.stdout, /run \/hooks/i);
|
|
assert.match(first.stdout, /restart active sessions/i);
|
|
const raw = await readFile(fixture.globalPath, 'utf-8');
|
|
assert.match(raw, /\r\n/);
|
|
const settings = JSON.parse(raw);
|
|
assert.equal(settings.theme, 'dark');
|
|
assert.deepEqual(settings.hooks.FutureEvent, [null]);
|
|
assert.equal(settings.hooks.PreToolUse[0].hooks[0].command, 'user-hook');
|
|
assertManagedTriple(settings);
|
|
assert.equal(await readFile(fixture.projectPath, 'utf-8'), projectBefore);
|
|
assert.equal(await readFile(fixture.localPath, 'utf-8'), localBefore);
|
|
for (const filePath of [
|
|
fixture.guardScriptPath,
|
|
fixture.hookScriptPath,
|
|
path.join(fixture.agentDir, 'config.json'),
|
|
path.join(fixture.agentDir, 'private.key'),
|
|
]) await lstat(filePath);
|
|
const before = await readFile(fixture.globalPath, 'utf-8');
|
|
assert.equal((await fixture.install()).code, 0);
|
|
assert.equal(await readFile(fixture.globalPath, 'utf-8'), before);
|
|
} finally {
|
|
await fixture.close();
|
|
}
|
|
});
|
|
|
|
test('Letta validates known hook schemas and preserves future events', async () => {
|
|
const fixture = await createFixture({ globalSettings: {
|
|
hooks: {
|
|
FutureEvent: [null],
|
|
PreToolUse: [{ matcher: '[', hooks: [{
|
|
type: 'command',
|
|
command: 'user-command',
|
|
timeout: 0,
|
|
quiet: false,
|
|
futureField: true,
|
|
}] }],
|
|
Stop: [{ hooks: [{
|
|
type: 'prompt',
|
|
prompt: 'Evaluate $ARGUMENTS',
|
|
model: 'fast',
|
|
timeout: 30,
|
|
}] }],
|
|
},
|
|
} });
|
|
try {
|
|
const result = await fixture.install();
|
|
assert.equal(result.code, 0, result.stderr);
|
|
const settings = await readSettings(fixture.globalPath);
|
|
assert.deepEqual(settings.hooks.FutureEvent, [null]);
|
|
assert.equal(settings.hooks.PreToolUse[0].matcher, '[');
|
|
assert.equal(settings.hooks.PreToolUse[0].hooks[0].futureField, true);
|
|
assert.equal(settings.hooks.Stop[0].hooks[0].type, 'prompt');
|
|
} finally {
|
|
await fixture.close();
|
|
}
|
|
});
|
|
|
|
test('Letta rejects malformed affected settings before runtime writes', async (t) => {
|
|
const cases = [
|
|
['syntax', '{ malformed', /parse Letta Code global settings/i],
|
|
['root', '[]', /must contain a JSON object/i],
|
|
['comment', '{ // comment\n "hooks": {} }', /parse Letta Code global settings/i],
|
|
['trailing comma', '{ "theme": true, }', /parse Letta Code global settings/i],
|
|
['duplicate', '{ "hooks": {}, "hooks": {} }', /duplicate field "hooks"/i],
|
|
['hooks shape', '{ "hooks": [] }', /field "hooks" must be an object/i],
|
|
['disabled', '{ "hooks": { "disabled": "yes" } }', /hooks.disabled.*boolean/i],
|
|
['event shape', '{ "hooks": { "PreToolUse": null } }', /must be an array/i],
|
|
['group shape', '{ "hooks": { "PreToolUse": [null] } }', /group.*object/i],
|
|
['tool matcher', '{ "hooks": { "PreToolUse": [{ "hooks": [] }] } }', /matcher must be a string/i],
|
|
['simple matcher', '{ "hooks": { "Stop": [{ "matcher": "*", "hooks": [] }] } }', /matcher is unsupported/i],
|
|
['hooks missing', '{ "hooks": { "PreToolUse": [{ "matcher": "*" }] } }', /hooks array/i],
|
|
['handler shape', '{ "hooks": { "Stop": [{ "hooks": [null] }] } }', /hooks\[0\].*object/i],
|
|
['handler type', '{ "hooks": { "Stop": [{ "hooks": [{ "type": "http" }] }] } }', /unsupported type/i],
|
|
['command', '{ "hooks": { "PreToolUse": [{ "matcher": "*", "hooks": [{ "type": "command" }] }] } }', /non-empty command/i],
|
|
['prompt', '{ "hooks": { "Stop": [{ "hooks": [{ "type": "prompt" }] }] } }', /non-empty prompt/i],
|
|
['model', '{ "hooks": { "Stop": [{ "hooks": [{ "type": "prompt", "prompt": "x", "model": 1 }] }] } }', /model must be a string/i],
|
|
['quiet', '{ "hooks": { "Stop": [{ "hooks": [{ "type": "command", "command": "x", "quiet": 1 }] }] } }', /quiet must be a boolean/i],
|
|
['timeout', '{ "hooks": { "Stop": [{ "hooks": [{ "type": "command", "command": "x", "timeout": -1 }] }] } }', /non-negative finite number/i],
|
|
];
|
|
for (const [name, source, pattern] of cases) {
|
|
await t.test(name, async () => {
|
|
const fixture = await createFixture({ globalSettings: source });
|
|
try {
|
|
const result = await fixture.install();
|
|
assert.equal(result.code, 1);
|
|
assert.match(result.stderr, pattern);
|
|
assert.equal(await readFile(fixture.globalPath, 'utf-8'), source);
|
|
await assertMissing(fixture.agentDir);
|
|
} finally {
|
|
await fixture.close();
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
test('Letta surfaces malformed project sources before writes', async (t) => {
|
|
for (const field of ['projectSettings', 'localSettings']) {
|
|
await t.test(field, async () => {
|
|
const fixture = await createFixture({ [field]: '{ malformed' });
|
|
try {
|
|
const result = await fixture.install();
|
|
assert.equal(result.code, 1);
|
|
assert.match(result.stderr, /parse Letta Code (project|project-local) settings/i);
|
|
await assertMissing(fixture.globalPath);
|
|
await assertMissing(fixture.agentDir);
|
|
} finally {
|
|
await fixture.close();
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
test('Letta applies the official hooks.disabled precedence', async (t) => {
|
|
const cases = [
|
|
['global true', { globalSettings: { hooks: { disabled: true } } }, 1],
|
|
['project true', { projectSettings: { hooks: { disabled: true } } }, 1],
|
|
['local true', { localSettings: { hooks: { disabled: true } } }, 1],
|
|
['global false override', {
|
|
globalSettings: { hooks: { disabled: false } },
|
|
projectSettings: { hooks: { disabled: true } },
|
|
localSettings: { hooks: { disabled: true } },
|
|
}, 0],
|
|
];
|
|
for (const [name, options, expectedCode] of cases) {
|
|
await t.test(name, async () => {
|
|
const fixture = await createFixture(options);
|
|
try {
|
|
const result = await fixture.install();
|
|
assert.equal(result.code, expectedCode, result.stderr);
|
|
if (expectedCode === 1) {
|
|
assert.match(result.stderr, /hooks.disabled/i);
|
|
await assertMissing(fixture.agentDir);
|
|
}
|
|
} finally {
|
|
await fixture.close();
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
test('Letta migrates exact legacy handlers and preserves ownership lookalikes', async () => {
|
|
const fixture = await createFixture({ globalSettings: {} });
|
|
const legacy = (scriptPath) => ({
|
|
matcher: '*',
|
|
hooks: [{ type: 'command', command: `node "${scriptPath}"` }],
|
|
});
|
|
const lookalike = legacy(fixture.guardScriptPath);
|
|
lookalike.hooks[0].quiet = true;
|
|
await writeFile(fixture.globalPath, `${JSON.stringify({ hooks: {
|
|
PreToolUse: [legacy(fixture.guardScriptPath), lookalike],
|
|
PostToolUse: [legacy(fixture.hookScriptPath)],
|
|
} }, null, 2)}\n`, { mode: 0o600 });
|
|
try {
|
|
assert.equal((await fixture.install()).code, 0);
|
|
let settings = await readSettings(fixture.globalPath);
|
|
assertManagedTriple(settings);
|
|
assert(settings.hooks.PreToolUse.some((group) => group.hooks[0].quiet === true));
|
|
const managed = settings.hooks.PreToolUse.find((group) => group.hooks[0].timeout === 10_000);
|
|
managed.userField = 'preserve-group';
|
|
managed.hooks.push({ type: 'command', command: 'user-command' });
|
|
await writeFile(fixture.globalPath, `${JSON.stringify(settings, null, 2)}\n`);
|
|
assert.equal((await runPlugin(fixture, 'uninstall', 'agent-1')).code, 0);
|
|
settings = await readSettings(fixture.globalPath);
|
|
assert(settings.hooks.PreToolUse.some((group) => group.hooks[0].quiet === true));
|
|
assert(settings.hooks.PreToolUse.some((group) => (
|
|
group.hooks.some((handler) => handler.command === 'user-command')
|
|
)));
|
|
assert.equal(settings.hooks.PostToolUse, undefined);
|
|
assert.equal(settings.hooks.PostToolUseFailure, undefined);
|
|
} finally {
|
|
await fixture.close();
|
|
}
|
|
});
|
|
|
|
test('Letta status requires exact hooks and strict runtime identity', async () => {
|
|
const fixture = await createFixture({ projectSettings: {} });
|
|
try {
|
|
assert.equal((await fixture.install()).code, 0);
|
|
let status = JSON.parse((await runPlugin(fixture, 'status', null)).stdout);
|
|
assert.equal(status.installed, true);
|
|
await writeFile(fixture.projectPath, '{"hooks":{"disabled":true}}\n');
|
|
status = JSON.parse((await runPlugin(fixture, 'status', null)).stdout);
|
|
assert.equal(status.hookConfigured, false);
|
|
await writeFile(fixture.projectPath, '{}\n');
|
|
status = JSON.parse((await runPlugin(fixture, 'status', null)).stdout);
|
|
assert.equal(status.installed, true);
|
|
const settings = await readSettings(fixture.globalPath);
|
|
settings.hooks.PostToolUseFailure.push(settings.hooks.PostToolUseFailure.at(-1));
|
|
await writeFile(fixture.globalPath, `${JSON.stringify(settings, null, 2)}\n`);
|
|
status = JSON.parse((await runPlugin(fixture, 'status', null)).stdout);
|
|
assert.equal(status.installed, false);
|
|
assert.equal((await fixture.install()).code, 0);
|
|
await writeFile(path.join(fixture.agentDir, 'private.key'), 'invalid');
|
|
const invalid = await runPlugin(fixture, 'status', null);
|
|
assert.equal(invalid.code, 1);
|
|
assert.match(invalid.stderr, /private key is invalid/i);
|
|
assert.equal((await fixture.install()).code, 0);
|
|
await writeFile(fixture.hookScriptPath, 'tampered');
|
|
status = JSON.parse((await runPlugin(fixture, 'status', null)).stdout);
|
|
assert.equal(status.installed, false);
|
|
} finally {
|
|
await fixture.close();
|
|
}
|
|
});
|
|
|
|
test('Letta uninstall removes a settings file containing only managed hooks', async () => {
|
|
const fixture = await createFixture();
|
|
try {
|
|
assert.equal((await fixture.install()).code, 0);
|
|
assert.equal((await runPlugin(fixture, 'uninstall', 'agent-1')).code, 0);
|
|
await assertMissing(fixture.globalPath);
|
|
} finally {
|
|
await fixture.close();
|
|
}
|
|
});
|
|
|
|
test('Letta CLI completes install, status, and uninstall end to end', async () => {
|
|
const fixture = await createFixture({ globalSettings: { theme: 'dark' } });
|
|
const privateKeyFile = path.join(fixture.rootDir, 'install-private.key');
|
|
const tokenFile = path.join(fixture.rootDir, 'install-token.txt');
|
|
try {
|
|
await writeFile(privateKeyFile, `${VALID_PRIVATE_KEY}\n`, { mode: 0o600 });
|
|
await writeFile(tokenFile, 'token-1\n', { mode: 0o600 });
|
|
const install = await runNode([
|
|
'--no-warnings', cliPath, 'install',
|
|
'--agent', 'letta',
|
|
'--org_id', 'org-1',
|
|
'--agent_id', 'agent-1',
|
|
'--kid', 'kid-1',
|
|
'--private_key_file', privateKeyFile,
|
|
'--token_file', tokenFile,
|
|
'--base_url', fixture.baseUrl,
|
|
], environment(fixture), fixture.projectDir);
|
|
assert.equal(install.code, 0, install.stderr);
|
|
assertManagedTriple(await readSettings(fixture.globalPath));
|
|
const status = await runNode(
|
|
['--no-warnings', cliPath, 'status'],
|
|
environment(fixture),
|
|
fixture.projectDir,
|
|
);
|
|
assert.equal(status.code, 0, status.stderr);
|
|
assert.match(status.stdout, /Letta Code \(letta\) \[installed\]/);
|
|
const uninstall = await runNode([
|
|
'--no-warnings', cliPath, 'uninstall',
|
|
'--agent', 'letta',
|
|
'--agent_id', 'agent-1',
|
|
], environment(fixture), fixture.projectDir);
|
|
assert.equal(uninstall.code, 0, uninstall.stderr);
|
|
assert.deepEqual(await readSettings(fixture.globalPath), { theme: 'dark' });
|
|
await assertMissing(fixture.agentDir);
|
|
} finally {
|
|
await fixture.close();
|
|
}
|
|
});
|