feat: support compact command across IM channels

This commit is contained in:
xmanrui 2026-08-18 23:20:05 +08:00
parent 4545cfe9c5
commit 1cc7a26f30
40 changed files with 728 additions and 136 deletions

View file

@ -2,7 +2,7 @@ import { createProductionController } from './production.mjs';
import { installDingtalkRpc } from './rpc.mjs';
export const name = 'dsh-dingtalk-host';
export const inject = ['connection', 'credentials', 'webServer'];
export const inject = ['connection', 'credentials', 'webServer', 'typertGateway'];
export async function apply(ctx, config = {}) {
if (config?.controller) {

View file

@ -15,6 +15,7 @@ import {
observeBotWorkspaceRemovals,
} from '../../../../src/channels/shared/bot-workspace-store.mjs';
import { createConnectionSupervisor } from './connection-supervisor.mjs';
import { createHarnessCommandExecutor } from '../../harness-command-executor.mjs';
function harnessOrigin(webServer, configured) {
if (configured !== undefined) return new URL(configured);
@ -79,12 +80,14 @@ export async function createProductionController(ctx, config = {}, internals = {
}
return state;
};
const commandExecutor = createHarnessCommandExecutor(ctx, internals.commandExecutor);
const harness = new Harness({
baseUrl: harnessOrigin(ctx.webServer, config.harnessBaseUrl),
workspace: defaultWorkspace,
agentPreset: config.agentPreset ?? 'standard',
autostart: false,
dshBin: config.dshBin ?? 'dsh',
...(commandExecutor ? { commandExecutor } : {}),
});
const coreController = new Controller({
deviceAuth,

View file

@ -2,7 +2,7 @@ import { createProductionController } from './production.mjs';
import { installDiscordRpc } from './rpc.mjs';
export const name = 'dsh-im-discord-host';
export const inject = ['connection', 'credentials', 'webServer'];
export const inject = ['connection', 'credentials', 'webServer', 'typertGateway'];
export async function apply(ctx, config = {}) {
if (config?.controller) {

View file

@ -3,7 +3,7 @@ import { createProductionController } from './production.mjs';
import { installFeishuRpc } from './rpc.mjs';
export const name = 'dsh-feishu-host';
export const inject = ['connection', 'credentials', 'webServer'];
export const inject = ['connection', 'credentials', 'webServer', 'typertGateway'];
function controllerFrom(ctx, config) {
if (config?.controller) return config.controller;

View file

@ -3,6 +3,7 @@ import { join, resolve } from 'node:path';
import { unlink } from 'node:fs/promises';
import * as Lark from '@larksuiteoapi/node-sdk';
import { createConnectionSupervisor } from './connection-supervisor.mjs';
import { createHarnessCommandExecutor } from '../../harness-command-executor.mjs';
import { verifyFeishuApp } from '../../../../src/channels/feishu/feishu-app.mjs';
import { FeishuRuntime } from '../../../../src/channels/feishu/feishu-runtime.mjs';
import { HarnessClient } from '../../../../src/channels/feishu/harness-client.mjs';
@ -105,6 +106,7 @@ export async function createProductionController(ctx, config = {}, internals = {
if (!botConfig) throw new Error('Unknown Feishu bot');
return stateFor(botConfig);
};
const commandExecutor = createHarnessCommandExecutor(ctx, internals.commandExecutor);
const harness = new Harness({
baseUrl: harnessOrigin(ctx.webServer, config.harnessBaseUrl),
workspace: defaultWorkspace,
@ -113,6 +115,7 @@ export async function createProductionController(ctx, config = {}, internals = {
// second DSH would create a competing server and lifecycle.
autostart: false,
dshBin: config.dshBin ?? 'dsh',
...(commandExecutor ? { commandExecutor } : {}),
});
const coreController = new Controller({

View file

@ -2,7 +2,7 @@ import { createProductionController } from './production.mjs';
import { installQqRpc } from './rpc.mjs';
export const name = 'dsh-im-qq-host';
export const inject = ['connection', 'credentials', 'webServer'];
export const inject = ['connection', 'credentials', 'webServer', 'typertGateway'];
export async function apply(ctx, config = {}) {
if (config?.controller) {

View file

@ -15,6 +15,7 @@ import {
observeBotWorkspaceRemovals,
} from '../../../../src/channels/shared/bot-workspace-store.mjs';
import { createConnectionSupervisor } from './connection-supervisor.mjs';
import { createHarnessCommandExecutor } from '../../harness-command-executor.mjs';
function harnessOrigin(webServer, configured) {
if (configured !== undefined) return new URL(configured);
@ -70,12 +71,14 @@ export async function createProductionController(ctx, config = {}, internals = {
}
return state;
};
const commandExecutor = createHarnessCommandExecutor(ctx, internals.commandExecutor);
const harness = new Harness({
baseUrl: harnessOrigin(ctx.webServer, config.harnessBaseUrl),
workspace: defaultWorkspace,
agentPreset: config.agentPreset ?? 'standard',
autostart: false,
dshBin: config.dshBin ?? 'dsh',
...(commandExecutor ? { commandExecutor } : {}),
});
const coreController = new Controller({
qrAuth,

View file

@ -3,6 +3,7 @@ import { homedir } from 'node:os';
import { join, resolve } from 'node:path';
import { createTokenConnectionSupervisor } from './connection-supervisor.mjs';
import { createHarnessCommandExecutor } from '../../harness-command-executor.mjs';
import {
BotWorkspaceStore,
createBotWorkspaceScope,
@ -64,12 +65,14 @@ export async function createTokenProductionController(ctx, config, internals, de
}
return state;
};
const commandExecutor = createHarnessCommandExecutor(ctx, internals.commandExecutor);
const harness = new ResolvedHarness({
baseUrl: harnessOrigin(ctx.webServer, config.harnessBaseUrl),
workspace: defaultWorkspace,
agentPreset: config.agentPreset ?? 'standard',
autostart: false,
dshBin: config.dshBin ?? 'dsh',
...(commandExecutor ? { commandExecutor } : {}),
});
const coreController = new ResolvedController({
credentials: ctx.credentials,

View file

@ -2,7 +2,7 @@ import { createProductionController } from './production.mjs';
import { installSlackRpc } from './rpc.mjs';
export const name = 'dsh-im-slack-host';
export const inject = ['connection', 'credentials', 'webServer'];
export const inject = ['connection', 'credentials', 'webServer', 'typertGateway'];
export async function apply(ctx, config = {}) {
if (config?.controller) return installSlackRpc(ctx, config.controller, config.rpcAuthority);

View file

@ -14,6 +14,7 @@ import {
} from '../../../../src/channels/shared/bot-workspace-store.mjs';
import { createTokenConnectionSupervisor } from '../shared/connection-supervisor.mjs';
import { harnessOrigin, pluginPaths } from '../shared/production.mjs';
import { createHarnessCommandExecutor } from '../../harness-command-executor.mjs';
export async function createProductionController(ctx, config = {}, internals = {}) {
if (!ctx?.credentials) throw new TypeError('dsh-im slack requires ctx.credentials');
@ -49,12 +50,14 @@ export async function createProductionController(ctx, config = {}, internals = {
}
return state;
};
const commandExecutor = createHarnessCommandExecutor(ctx, internals.commandExecutor);
const harness = new ResolvedHarness({
baseUrl: harnessOrigin(ctx.webServer, config.harnessBaseUrl),
workspace: defaultWorkspace,
agentPreset: config.agentPreset ?? 'standard',
autostart: false,
dshBin: config.dshBin ?? 'dsh',
...(commandExecutor ? { commandExecutor } : {}),
});
const coreController = new ResolvedController({
credentials: ctx.credentials,

View file

@ -2,7 +2,7 @@ import { createProductionController } from './production.mjs';
import { installTelegramRpc } from './rpc.mjs';
export const name = 'dsh-im-telegram-host';
export const inject = ['connection', 'credentials', 'webServer'];
export const inject = ['connection', 'credentials', 'webServer', 'typertGateway'];
export async function apply(ctx, config = {}) {
if (config?.controller) {

View file

@ -2,7 +2,7 @@ import { createProductionController } from './production.mjs';
import { installWecomRpc } from './rpc.mjs';
export const name = 'dsh-im-wecom-host';
export const inject = ['connection', 'credentials', 'webServer'];
export const inject = ['connection', 'credentials', 'webServer', 'typertGateway'];
export async function apply(ctx, config = {}) {
if (config?.controller) {

View file

@ -15,6 +15,7 @@ import {
observeBotWorkspaceRemovals,
} from '../../../../src/channels/shared/bot-workspace-store.mjs';
import { createConnectionSupervisor } from './connection-supervisor.mjs';
import { createHarnessCommandExecutor } from '../../harness-command-executor.mjs';
function harnessOrigin(webServer, configured) {
if (configured !== undefined) return new URL(configured);
@ -73,12 +74,14 @@ export async function createProductionController(ctx, config = {}, internals = {
}
return state;
};
const commandExecutor = createHarnessCommandExecutor(ctx, internals.commandExecutor);
const harness = new Harness({
baseUrl: harnessOrigin(ctx.webServer, config.harnessBaseUrl),
workspace: defaultWorkspace,
agentPreset: config.agentPreset ?? 'standard',
autostart: false,
dshBin: config.dshBin ?? 'dsh',
...(commandExecutor ? { commandExecutor } : {}),
});
const coreController = new Controller({
qrAuth,

View file

@ -2,7 +2,7 @@ import { createProductionController } from './production.mjs';
import { installWeixinRpc } from './rpc.mjs';
export const name = 'dsh-weixin-host';
export const inject = ['connection', 'credentials', 'webServer'];
export const inject = ['connection', 'credentials', 'webServer', 'typertGateway'];
export async function apply(ctx, config = {}) {
if (config?.controller) {

View file

@ -15,6 +15,7 @@ import {
observeBotWorkspaceRemovals,
} from '../../../../src/channels/shared/bot-workspace-store.mjs';
import { createConnectionSupervisor } from './connection-supervisor.mjs';
import { createHarnessCommandExecutor } from '../../harness-command-executor.mjs';
function harnessOrigin(webServer, configured) {
if (configured !== undefined) return new URL(configured);
@ -73,12 +74,14 @@ export async function createProductionController(ctx, config = {}, internals = {
}
return state;
};
const commandExecutor = createHarnessCommandExecutor(ctx, internals.commandExecutor);
const harness = new Harness({
baseUrl: harnessOrigin(ctx.webServer, config.harnessBaseUrl),
workspace: defaultWorkspace,
agentPreset: config.agentPreset ?? 'standard',
autostart: false,
dshBin: config.dshBin ?? 'dsh',
...(commandExecutor ? { commandExecutor } : {}),
});
const coreController = new Controller({
api,

View file

@ -2,7 +2,7 @@ import { createProductionController } from './production.mjs';
import { installWhatsappRpc } from './rpc.mjs';
export const name = 'dsh-im-whatsapp-host';
export const inject = ['connection', 'webServer'];
export const inject = ['connection', 'webServer', 'typertGateway'];
export async function apply(ctx, config = {}) {
if (config?.controller) {

View file

@ -15,6 +15,7 @@ import {
observeBotWorkspaceRemovals,
} from '../../../../src/channels/shared/bot-workspace-store.mjs';
import { createTokenConnectionSupervisor } from '../shared/connection-supervisor.mjs';
import { createHarnessCommandExecutor } from '../../harness-command-executor.mjs';
const AUTH_DIRECTORY_PATTERN = /^[a-f0-9-]{36}$/;
@ -76,12 +77,14 @@ export async function createProductionController(ctx, config = {}, internals = {
}
return state;
};
const commandExecutor = createHarnessCommandExecutor(ctx, internals.commandExecutor);
const harness = new Harness({
baseUrl: harnessOrigin(ctx.webServer, config.harnessBaseUrl),
workspace: defaultWorkspace,
agentPreset: config.agentPreset ?? 'standard',
autostart: false,
dshBin: config.dshBin ?? 'dsh',
...(commandExecutor ? { commandExecutor } : {}),
});
const coreController = new Controller({
configStore: observedConfigStore,

View file

@ -0,0 +1,21 @@
/**
* Adapt the Host's Typert command gateway to the channel Harness client.
* Programmatic production fixtures may omit the gateway and exercise other assembly paths.
*/
export function createHarnessCommandExecutor(ctx, provided) {
if (provided !== undefined) {
if (typeof provided !== 'function') throw new TypeError('commandExecutor must be a function');
return provided;
}
const gateway = ctx?.typertGateway;
if (!gateway) return undefined;
if (typeof gateway.invoke !== 'function') {
throw new TypeError('dsh-im requires a callable ctx.typertGateway');
}
return (sessionId, line, options = {}) => gateway.invoke({
namespace: 'commands',
method: 'execute',
args: { agentId: sessionId, line },
signal: options.signal,
});
}

View file

@ -9,7 +9,7 @@ import { apply as applyWeixin } from './channels/weixin/index.mjs';
import { apply as applyWhatsapp } from './channels/whatsapp/index.mjs';
export const name = 'dsh-im-host';
export const inject = ['connection', 'credentials', 'webServer'];
export const inject = ['connection', 'credentials', 'webServer', 'typertGateway'];
function channelConfig(config, name) {
const channel = config[name] ?? {};