mirror of
https://github.com/openclaw/lobster.git
synced 2026-08-14 00:48:09 +00:00
83 lines
3.1 KiB
TypeScript
83 lines
3.1 KiB
TypeScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { promises as fsp } from 'node:fs';
|
|
import os from 'node:os';
|
|
import path from 'node:path';
|
|
import { spawnSync } from 'node:child_process';
|
|
|
|
import { decodeResumeToken } from '../src/resume.js';
|
|
import { encodeToken } from '../src/token.js';
|
|
|
|
function runCli(args: string[], env: Record<string, string | undefined>) {
|
|
const bin = path.join(process.cwd(), 'bin', 'lobster.js');
|
|
return spawnSync('node', [bin, ...args], {
|
|
encoding: 'utf8',
|
|
env: { ...process.env, ...env },
|
|
});
|
|
}
|
|
|
|
test('state-backed resume token roundtrip and resume pipeline continues', async () => {
|
|
const tmpDir = await fsp.mkdtemp(path.join(os.tmpdir(), 'lobster-resume-'));
|
|
const stateDir = path.join(tmpDir, 'state');
|
|
|
|
const pipeline =
|
|
"exec --json --shell \"node -e 'process.stdout.write(JSON.stringify([{a:1}]))'\" | approve --prompt 'ok?' | pick a";
|
|
|
|
const first = runCli(['run', '--mode', 'tool', pipeline], { LOBSTER_STATE_DIR: stateDir });
|
|
assert.equal(first.status, 0);
|
|
const firstJson = JSON.parse(first.stdout);
|
|
assert.equal(firstJson.status, 'needs_approval');
|
|
assert.ok(firstJson.requiresApproval?.resumeToken);
|
|
|
|
const payload = decodeResumeToken(firstJson.requiresApproval.resumeToken);
|
|
assert.equal(payload.kind, 'pipeline-resume');
|
|
assert.equal(typeof payload.stateKey, 'string');
|
|
|
|
const resumed = runCli(
|
|
['resume', '--token', firstJson.requiresApproval.resumeToken, '--approve', 'yes'],
|
|
{ LOBSTER_STATE_DIR: stateDir },
|
|
);
|
|
assert.equal(resumed.status, 0);
|
|
const resumedJson = JSON.parse(resumed.stdout);
|
|
assert.equal(resumedJson.status, 'ok');
|
|
assert.deepEqual(resumedJson.output, [{ a: 1 }]);
|
|
});
|
|
|
|
test('decodeResumeToken rejects inline executable pipeline tokens', () => {
|
|
const forgedToken = encodeToken({
|
|
protocolVersion: 1,
|
|
v: 1,
|
|
pipeline: [{ name: 'exec', args: { shell: 'echo FORGED' }, raw: "exec --shell 'echo FORGED'" }],
|
|
resumeAtIndex: 0,
|
|
items: [],
|
|
prompt: 'ignored',
|
|
});
|
|
|
|
assert.throws(() => decodeResumeToken(forgedToken), /Invalid token/);
|
|
});
|
|
|
|
test('resume cancellation cleans up pipeline resume state', async () => {
|
|
const tmpDir = await fsp.mkdtemp(path.join(os.tmpdir(), 'lobster-resume-cancel-'));
|
|
const stateDir = path.join(tmpDir, 'state');
|
|
|
|
const pipeline =
|
|
"exec --json --shell \"node -e 'process.stdout.write(JSON.stringify([{a:1}]))'\" | approve --prompt 'ok?' | pick a";
|
|
|
|
const first = runCli(['run', '--mode', 'tool', pipeline], { LOBSTER_STATE_DIR: stateDir });
|
|
assert.equal(first.status, 0);
|
|
const firstJson = JSON.parse(first.stdout);
|
|
assert.equal(firstJson.status, 'needs_approval');
|
|
|
|
const cancelled = runCli(
|
|
['resume', '--token', firstJson.requiresApproval.resumeToken, '--approve', 'no'],
|
|
{ LOBSTER_STATE_DIR: stateDir },
|
|
);
|
|
assert.equal(cancelled.status, 0);
|
|
const cancelledJson = JSON.parse(cancelled.stdout);
|
|
assert.equal(cancelledJson.status, 'cancelled');
|
|
|
|
const files = await fsp.readdir(stateDir);
|
|
const pipelineResumeFiles = files.filter((name) => name.startsWith('pipeline_resume_'));
|
|
assert.deepEqual(pipelineResumeFiles, []);
|
|
});
|