dsh-im-ops/test/control-command.test.mjs
2026-08-25 12:25:01 +08:00

185 lines
6.4 KiB
JavaScript

import assert from 'node:assert/strict';
import test from 'node:test';
import {
isControlCommand,
runControlCommand,
} from '../src/channels/shared/control-command.mjs';
import { HarnessApprovalQueue } from '../src/channels/shared/harness-approval.mjs';
function fixture({ sessionId = 'session-one', stopped = true, steered = true } = {}) {
const calls = [];
const state = {
sessionFor(key) {
calls.push(['sessionFor', key]);
return sessionId;
},
};
const harness = {
workspaceSession(id) {
calls.push(['workspaceSession', id]);
return {
async stopActiveTurn(control, options) {
calls.push(['stopActiveTurn', id, control, options]);
return stopped;
},
async steerActiveTurn(text, control, options) {
calls.push(['steerActiveTurn', id, text, control, options]);
return steered;
},
};
},
};
return { calls, harness, state };
}
test('isControlCommand reserves valid and malformed control command forms', () => {
for (const value of [
'/stop', ' /STOP ', '/stop now', '/steer', '/StEeR do this', '/steer line one\nline two',
'/version', ' /VERSION ', '/version now',
]) {
assert.equal(isControlCommand(value), true, value);
}
for (const value of [null, '', 'stop', '/stopping', '/steering', '/versions', 'hello /stop']) {
assert.equal(isControlCommand(value), false, String(value));
}
});
test('/version returns the package version without touching Harness or Session state', async () => {
const { calls, harness, state } = fixture();
const { version } = await import('../package.json', { with: { type: 'json' } })
.then((module) => module.default);
assert.deepEqual(
await runControlCommand('/VERSION', harness, state, 'direct:one'),
{ message: `dsh-im v${version}` },
);
assert.match(
(await runControlCommand('/version details', harness, state, 'direct:one')).message,
/用法/,
);
assert.equal(calls.length, 0);
});
test('/stop is exact, text-only, and never creates a Session', async () => {
const { calls, harness, state } = fixture();
assert.match((await runControlCommand('/stop later', harness, state, 'direct:one')).message, /用法/);
assert.match((await runControlCommand('/stop', harness, state, 'direct:one', {
hasImages: true,
})).message, /纯文字/);
assert.equal(calls.length, 0);
});
test('/stop controls only the bound active turn and marks accepted cancellation', async () => {
const active = fixture();
const signal = new AbortController().signal;
const owner = {};
const control = { owner, key: 'direct:one' };
const result = await runControlCommand(
'/STOP',
active.harness,
active.state,
'direct:one',
{ signal, pendingInteraction: true, control },
);
assert.deepEqual(result, { message: '已请求停止当前任务。', stopped: true });
assert.deepEqual(active.calls, [
['sessionFor', 'direct:one'],
['workspaceSession', 'session-one'],
['stopActiveTurn', 'session-one', control, { signal }],
]);
const inactive = fixture({ stopped: false });
const missing = await runControlCommand(
'/stop', inactive.harness, inactive.state, 'direct:one', { control },
);
assert.match(missing.message, /没有正在运行/);
assert.equal(missing.stopped, undefined);
});
test('/stop and /steer return friendly no-session messages without creating one', async () => {
const { harness, state, calls } = fixture({ sessionId: null });
assert.match(
(await runControlCommand('/stop', harness, state, 'direct:one')).message,
/没有正在运行/,
);
assert.match(
(await runControlCommand('/steer 补充', harness, state, 'direct:one')).message,
/没有绑定会话/,
);
assert.equal(calls.some(([method]) => method === 'workspaceSession'), false);
});
test('/steer requires text, preserves multiple lines, and rejects images', async () => {
const { harness, state, calls } = fixture();
assert.match((await runControlCommand('/steer', harness, state, 'direct:one')).message, /用法/);
assert.match((await runControlCommand('/steer ', harness, state, 'direct:one')).message, /用法/);
assert.match((await runControlCommand('/steer text', harness, state, 'direct:one', {
hasImages: true,
})).message, /纯文字/);
assert.equal(calls.length, 0);
const control = { owner: {}, key: 'direct:one' };
const result = await runControlCommand(
'/steer first line\nsecond line',
harness,
state,
'direct:one',
{ control },
);
assert.match(result.message, /已提交/);
assert.deepEqual(calls.find(([method]) => method === 'steerActiveTurn'), [
'steerActiveTurn',
'session-one',
'first line\nsecond line',
control,
{},
]);
});
test('/steer never reaches Harness while an interaction is pending', async () => {
const { harness, state, calls } = fixture();
const result = await runControlCommand('/steer continue', harness, state, 'direct:one', {
pendingInteraction: true,
control: { owner: {}, key: 'direct:one' },
});
assert.match(result.message, /等待你的回答或审批/);
assert.equal(calls.length, 0);
});
test('/steer reports a lost active-turn race instead of starting new work', async () => {
const { harness, state, calls } = fixture({ steered: false });
const result = await runControlCommand('/steer continue', harness, state, 'direct:one', {
control: { owner: {}, key: 'direct:one' },
});
assert.match(result.message, /没有正在运行/);
assert.equal(calls.filter(([method]) => method === 'steerActiveTurn').length, 1);
});
test('HarnessApprovalQueue exposes whether a route has a live approval', async () => {
const queue = new HarnessApprovalQueue({ logger: { warn() {}, error() {} } });
const replies = [];
const interaction = {
kind: 'approval',
rpcId: 'approval-rpc',
sessionId: 'session-one',
toolCall: { callId: 'call-one', name: 'bash', arguments: '{}' },
payload: {
type: 'approval/requested',
sessionId: 'session-one',
approvalId: 'approval-one',
toolName: 'bash',
callId: 'call-one',
},
async respond() {},
};
await queue.handleRequested(interaction, {
key: 'direct:one',
actor: 'user-one',
send: async (text) => replies.push(text),
});
assert.equal(queue.hasPending('direct:one'), true);
assert.equal(queue.hasPending('direct:other'), false);
await queue.closeRoute('direct:one');
assert.equal(queue.hasPending('direct:one'), false);
});