@tmim/dsh-plugin-tmim 0.1.2 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +2 -1
- package/dist/index.mjs +26 -20
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -19,7 +19,8 @@ export type Credential = {
|
|
|
19
19
|
};
|
|
20
20
|
export type Config = {
|
|
21
21
|
baseUrl: string;
|
|
22
|
-
|
|
22
|
+
/** Optional until a TM IM connection is actually needed. */
|
|
23
|
+
credential?: Credential;
|
|
23
24
|
deviceId: string;
|
|
24
25
|
webhook: WebhookConfig;
|
|
25
26
|
enableAgentTakeover: boolean;
|
package/dist/index.mjs
CHANGED
|
@@ -539,39 +539,44 @@ function registerTmimTools(ctx, client) {
|
|
|
539
539
|
}
|
|
540
540
|
// src/index.ts
|
|
541
541
|
const Config = z.object({
|
|
542
|
-
baseUrl: z.string().
|
|
542
|
+
baseUrl: z.string().default("http://tmim.stkg.ltd/").description("TM IM 服务器地址(不带 /v1 后缀)。例如:https://tmim.example.com"),
|
|
543
543
|
credential: z.union([z.object({
|
|
544
|
-
kind: z.const("apikey"),
|
|
545
|
-
token: z.string().required()
|
|
544
|
+
kind: z.const("apikey").description("使用 Agent Takeover 的 api_key(sk_… 开头,长期有效)"),
|
|
545
|
+
token: z.string().role("secret").required().description("Agent Takeover api_key,在 TM IM 的「Agent 接管」页开启时获取")
|
|
546
546
|
}), z.object({
|
|
547
|
-
kind: z.const("password"),
|
|
548
|
-
username: z.string().required(),
|
|
549
|
-
password: z.string().required()
|
|
550
|
-
})])
|
|
551
|
-
|
|
547
|
+
kind: z.const("password").description("使用 TM IM 登录账号和密码"),
|
|
548
|
+
username: z.string().required().description("TM IM 登录账号"),
|
|
549
|
+
password: z.string().role("secret").required().description("TM IM 登录密码")
|
|
550
|
+
})]).default({
|
|
551
|
+
kind: "apikey",
|
|
552
|
+
token: ""
|
|
553
|
+
}).description("TM IM 连接凭证,可选;缺省时插件仍会启动,但只有配置凭证后才能收发消息"),
|
|
554
|
+
deviceId: z.string().default(`dsh-${process.pid}`).description("设备标识,会随请求上报(X-TM-Device-Id)"),
|
|
552
555
|
webhook: z.object({
|
|
553
|
-
enabled: z.boolean().default(false),
|
|
554
|
-
path: z.string().default("/tm/callback"),
|
|
555
|
-
secret: z.string().default(""),
|
|
556
|
-
maxSkewSeconds: z.number().default(300)
|
|
556
|
+
enabled: z.boolean().default(false).description("是否开启 WEBHOOK 实时接收消息(需要公网可达的 HTTPS endpoint)"),
|
|
557
|
+
path: z.string().default("/tm/callback").description("WEBHOOK 回调路径;对应 TM IM 的 endpoint_url"),
|
|
558
|
+
secret: z.string().role("secret").default("").description("WEBHOOK 签名密钥(webhook_secret),启用时必填;由 TM IM 签发"),
|
|
559
|
+
maxSkewSeconds: z.number().default(300).description("防重放的签名时间戳容忍偏差上限(秒)")
|
|
557
560
|
}),
|
|
558
|
-
enableAgentTakeover: z.boolean().default(false),
|
|
561
|
+
enableAgentTakeover: z.boolean().default(false).description("启动时是否自动 POST 开启 Agent Takeover 配置(需要已配置凭证)"),
|
|
559
562
|
agent: z.object({
|
|
560
|
-
provider: z.string().default("deepseek-official"),
|
|
561
|
-
model: z.string().
|
|
563
|
+
provider: z.string().default("deepseek-official").description("应答所用的模型提供商"),
|
|
564
|
+
model: z.string().default("deepseek-chat").description("应答所用的模型名,如 deepseek-chat")
|
|
562
565
|
}),
|
|
563
|
-
cwd: z.string().default(process.cwd()),
|
|
564
|
-
tools: z.boolean().default(true)
|
|
566
|
+
cwd: z.string().default(process.cwd()).description("工作目录"),
|
|
567
|
+
tools: z.boolean().default(true).description("是否向智能体注册主动式的 TM IM 工具(发消息/建会话等)")
|
|
565
568
|
});
|
|
566
569
|
const name = "tmim";
|
|
567
570
|
async function apply(ctx, config) {
|
|
568
571
|
if (config.webhook.enabled && !config.webhook.secret) throw new Error("[tmim] webhook.enabled requires webhook.secret (the TM IM webhook_secret)");
|
|
569
572
|
const client = new TmimClient({
|
|
570
573
|
baseUrl: config.baseUrl,
|
|
571
|
-
...config.credential
|
|
574
|
+
...config.credential?.kind === "apikey" && config.credential.token ? { token: config.credential.token } : {},
|
|
572
575
|
deviceId: config.deviceId
|
|
573
576
|
});
|
|
574
|
-
|
|
577
|
+
const hasCredential = config.credential?.kind === "apikey" && !!config.credential.token || config.credential?.kind === "password" && !!config.credential.password;
|
|
578
|
+
if (!hasCredential) ctx.logger.warn("[tmim] no credential configured — plugin started in idle mode. Configure credential (Agent api_key or password) to enable messaging.");
|
|
579
|
+
else if (config.credential.kind === "password") {
|
|
575
580
|
await client.login(config.credential.username, config.credential.password);
|
|
576
581
|
ctx.logger.info("[tmim] authenticated via password login");
|
|
577
582
|
} else {
|
|
@@ -579,7 +584,8 @@ async function apply(ctx, config) {
|
|
|
579
584
|
ctx.logger.info("[tmim] using Agent api key");
|
|
580
585
|
}
|
|
581
586
|
if (config.enableAgentTakeover) {
|
|
582
|
-
if (
|
|
587
|
+
if (!hasCredential) ctx.logger.warn("[tmim] enableAgentTakeover requires a credential; skipping takeover setup");
|
|
588
|
+
else if ((await client.configureAgentTakeover({
|
|
583
589
|
...config.webhook.enabled ? {
|
|
584
590
|
pushMode: 1,
|
|
585
591
|
endpointUrl: config.webhook.path
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":["newClientMsgId"],"sources":["../src/tmim-client.ts","../src/crypto.ts","../src/webhook.ts","../src/bridge.ts","../src/tools.ts","../src/index.ts"],"sourcesContent":["/**\r\n * Minimal TM IM REST client (user-end `/v1`) for a Harness agent.\r\n *\r\n * Implements the endpoints a connector needs:\r\n * - auth (login / refresh / agent takeover api key)\r\n * - direct conversation get-or-create\r\n * - group conversation create\r\n * - list conversations\r\n * - send a text message (with idempotent client_msg_id)\r\n * - incremental pull (since_seq) and cursor pull\r\n * - agent takeover configuration (auth + status + enable/disable WEBHOOK)\r\n *\r\n * All requests expect the TM IM unified envelope:\r\n * { \"code\": 0, \"message\": \"ok\", \"data\": {...} }\r\n * where a non-zero `code` is a business error returned over HTTP 200.\r\n */\r\n\r\nexport interface TmimConfig {\r\n /** Base URL, e.g. `https://tmim.example.com`. Do NOT append `/v1`. */\r\n baseUrl: string\r\n /** Authentication: either an Agent takeover api_key (`Bearer`) ... */\r\n token?: string\r\n /** ... or credentials used to log in and obtain a JWT. */\r\n username?: string\r\n password?: string\r\n /** Stable device id reported via `X-TM-Device-Id`. */\r\n deviceId?: string\r\n /** Custom fetch; defaults to global fetch. */\r\n fetch?: typeof fetch\r\n /** Timeout for each HTTP request, ms. */\r\n timeoutMs?: number\r\n}\r\n\r\ninterface TmimEnvelope<T> {\r\n code: number\r\n message: string\r\n data: T | null\r\n}\r\n\r\nexport class TmimError extends Error {\r\n constructor(\r\n /** Business code from the envelope. 0 means transport-level success. */\r\n public readonly code: number,\r\n message: string,\r\n public readonly httpStatus?: number,\r\n public readonly payload?: unknown,\r\n ) {\r\n super(message)\r\n this.name = 'TmimError'\r\n }\r\n}\r\n\r\nexport class TmimClient {\r\n private token: string | undefined\r\n private fetchImpl: typeof fetch\r\n private timeoutMs: number\r\n\r\n constructor(private readonly config: TmimConfig) {\r\n this.token = config.token\r\n this.fetchImpl = config.fetch ?? globalThis.fetch.bind(globalThis)\r\n this.timeoutMs = config.timeoutMs ?? 15_000\r\n }\r\n\r\n /** Current bearer token (may be a JWT or an Agent api_key). */\r\n get authToken(): string | undefined {\r\n return this.token\r\n }\r\n\r\n // ---- low level ----\r\n\r\n private async request<T>(\r\n method: string,\r\n path: string,\r\n body?: unknown,\r\n opts: { auth?: boolean; headers?: Record<string, string> } = {},\r\n ): Promise<T> {\r\n const url = `${this.config.baseUrl}/v1${path}`\r\n const headers: Record<string, string> = {\r\n Accept: 'application/json',\r\n ...(opts.headers ?? {}),\r\n }\r\n if (body !== undefined) headers['Content-Type'] = 'application/json; charset=utf-8'\r\n if (this.config.deviceId) headers['X-TM-Device-Id'] = this.config.deviceId\r\n if (opts.auth !== false && this.token) headers['Authorization'] = `Bearer ${this.token}`\r\n\r\n const controller = new AbortController()\r\n const timer = setTimeout(() => controller.abort(), this.timeoutMs)\r\n try {\r\n const res = await this.fetchImpl(url, {\r\n method,\r\n headers,\r\n ...(body === undefined ? {} : { body: JSON.stringify(body) }),\r\n signal: controller.signal,\r\n })\r\n let envelope: TmimEnvelope<T> | undefined\r\n let text = ''\r\n try {\r\n text = await res.text()\r\n envelope = JSON.parse(text) as TmimEnvelope<T>\r\n } catch {\r\n envelope = undefined\r\n }\r\n\r\n if (res.status === 401 || res.status === 403) {\r\n throw new TmimError(res.status, `tmim auth failed (HTTP ${res.status})`, res.status, text)\r\n }\r\n if (!res.ok && !envelope) {\r\n throw new TmimError(0, `tmim HTTP ${res.status}: ${text.slice(0, 300)}`, res.status, text)\r\n }\r\n if (envelope && envelope.code !== 0) {\r\n throw new TmimError(envelope.code, envelope.message ?? `tmim error ${envelope.code}`, res.status, envelope)\r\n }\r\n return envelope?.data ?? (undefined as T)\r\n } finally {\r\n clearTimeout(timer)\r\n }\r\n }\r\n\r\n // ---- auth ----\r\n\r\n /** Login with handle/password; stores the returned JWT. */\r\n async login(handle: string, password: string): Promise<{ token: string; refreshToken: string }> {\r\n const data = await this.request<{\r\n token: string\r\n refresh_token: string\r\n actor: unknown\r\n }>('POST', '/auth/login', { handle, password }, { auth: false })\r\n this.token = data.token\r\n return { token: data.token, refreshToken: data.refresh_token }\r\n }\r\n\r\n /** Exchange a refresh token for a new access token (refresh_token is one-time). */\r\n async refresh(refreshToken: string): Promise<{ token: string; refreshToken: string }> {\r\n const data = await this.request<{ token: string; refresh_token: string }>(\r\n 'POST',\r\n '/auth/refresh',\r\n { refresh_token: refreshToken },\r\n { auth: false },\r\n )\r\n this.token = data.token\r\n return { token: data.token, refreshToken: data.refresh_token }\r\n }\r\n\r\n /** Set an explicit bearer token (e.g. an Agent takeover api_key). */\r\n setToken(token: string): this {\r\n this.token = token\r\n return this\r\n }\r\n\r\n // ---- agent takeover ----\r\n\r\n /**\r\n * Configure Agent Takeover.\r\n * doc: POST /v1/me/agent-takeover\r\n * Returns the issued webhook_secret ONLY when the merged push mode is\r\n * WEBHOOK and no secret existed yet (shown exactly once).\r\n */\r\n async configureAgentTakeover(args: {\r\n pushMode: 1 | 2 | 3 // 1=WEBHOOK, 2=LONG_POLL, 3=PULL\r\n endpointUrl?: string\r\n capabilities?: string[]\r\n modelInfo?: string\r\n rateLimit?: number\r\n }): Promise<{ webhookSecret?: string }> {\r\n const body: Record<string, unknown> = { push_mode: args.pushMode }\r\n if (args.endpointUrl !== undefined) body.endpoint_url = args.endpointUrl\r\n if (args.capabilities !== undefined) body.capabilities = args.capabilities\r\n if (args.modelInfo !== undefined) body.model_info = args.modelInfo\r\n if (args.rateLimit !== undefined) body.rate_limit = args.rateLimit\r\n return this.request<{ webhookSecret?: string }>('POST', '/me/agent-takeover', body)\r\n }\r\n\r\n /** Rotate the webhook secret; returns the new plaintext exactly once. */\r\n async rotateWebhookSecret(): Promise<{ webhookSecret: string }> {\r\n return this.request<{ webhookSecret: string }>(\r\n 'POST',\r\n '/me/agent-takeover/rotate-webhook-secret',\r\n )\r\n }\r\n\r\n /** Disable Agent Takeover entirely. */\r\n async disableAgentTakeover(): Promise<void> {\r\n await this.request<null>('DELETE', '/me/agent-takeover')\r\n }\r\n\r\n /** Read current takeover config (never includes secrets). */\r\n async getAgentTakeoverStatus(): Promise<{\r\n actorId: number\r\n enabled: boolean\r\n pushMode: number\r\n endpointUrl?: string\r\n hasApiKey: boolean\r\n hasWebhookSecret: boolean\r\n }> {\r\n const d = await this.request<{\r\n actor_id: number\r\n enabled: boolean\r\n push_mode: number\r\n endpoint_url?: string\r\n has_api_key: boolean\r\n has_webhook_secret: boolean\r\n }>('GET', '/me/agent-takeover')\r\n return {\r\n actorId: d.actor_id,\r\n enabled: d.enabled,\r\n pushMode: d.push_mode,\r\n ...(d.endpoint_url === undefined ? {} : { endpointUrl: d.endpoint_url }),\r\n hasApiKey: d.has_api_key,\r\n hasWebhookSecret: d.has_webhook_secret,\r\n }\r\n }\r\n\r\n // ---- conversations ----\r\n\r\n /** Get or create a direct (1:1) conversation with a peer. */\r\n async getOrCreateDirectConversation(peer: string): Promise<{\r\n convId: number\r\n convType: number\r\n peer: { actor_id: number; handle: string; display_name: string; online: boolean }\r\n created: boolean\r\n lastSeq: number\r\n }> {\r\n const d = await this.request<{\r\n conv_id: number\r\n conv_type: number\r\n peer: { actor_id: number; handle: string; display_name: string; online: boolean }\r\n created: boolean\r\n last_seq: number\r\n }>('POST', '/conversations/direct', { peer })\r\n return {\r\n convId: d.conv_id,\r\n convType: d.conv_type,\r\n peer: d.peer,\r\n created: d.created,\r\n lastSeq: d.last_seq,\r\n }\r\n }\r\n\r\n /** Create a group conversation. `members` excludes the caller. */\r\n async createGroupConversation(title: string, members: string[]): Promise<{ convId: number }> {\r\n const d = await this.request<{ conv_id: number }>('POST', '/conversations/group', { title, members })\r\n return { convId: d.conv_id }\r\n }\r\n\r\n /** List the caller's conversations (cursor paginated). */\r\n async listConversations(limit = 50, cursor?: string): Promise<{\r\n items: Array<{ conv_id: number; conv_type: number; title: string | null; last_seq: number }>\r\n nextCursor: string | null\r\n }> {\r\n const q = new URLSearchParams({ limit: String(limit) })\r\n if (cursor) q.set('cursor', cursor)\r\n const d = await this.request<{ items: Array<{ conv_id: number } & Record<string, unknown>>; next_cursor: string | null }>('GET', `/conversations?${q}`)\r\n return { items: d.items as any, nextCursor: d.next_cursor }\r\n }\r\n\r\n // ---- messages ----\r\n\r\n /**\r\n * Send a text message. `clientMsgId` is the idempotency key; re-sending the\r\n * same key returns the identical original message (no duplicate).\r\n */\r\n async sendText(convId: number, text: string, clientMsgId: string, replyTo?: number): Promise<{\r\n messageId: number\r\n convId: number\r\n seq: number\r\n clientMsgId: string\r\n senderId: number\r\n createdAt: string\r\n }> {\r\n const d = await this.request<{\r\n message_id: number\r\n conv_id: number\r\n seq: number\r\n client_msg_id: string\r\n sender_id: number\r\n created_at: string\r\n }>('POST', `/conversations/${convId}/messages`, {\r\n msg_type: 'TEXT',\r\n client_msg_id: clientMsgId,\r\n content: { text },\r\n ...(replyTo === undefined ? {} : { reply_to: replyTo }),\r\n })\r\n return {\r\n messageId: d.message_id,\r\n convId: d.conv_id,\r\n seq: d.seq,\r\n clientMsgId: d.client_msg_id,\r\n senderId: d.sender_id,\r\n createdAt: d.created_at,\r\n }\r\n }\r\n\r\n /** Incremental pull: messages with seq > sinceSeq, ascending. */\r\n async pullMessagesSince(convId: number, sinceSeq: number, limit = 200): Promise<Array<{\r\n message_id: number\r\n conv_id: number\r\n seq: number\r\n sender_id: number\r\n msg_type: string\r\n content: { text?: string }\r\n created_at: string\r\n }>> {\r\n const q = new URLSearchParams({ since_seq: String(sinceSeq), limit: String(limit) })\r\n const d = await this.request<{ items: Array<any> }>('GET', `/conversations/${convId}/messages?${q}`)\r\n return d.items ?? []\r\n }\r\n\r\n /** Cursor pull: newest-first history page. */\r\n async pullConversationHistory(convId: number, cursor?: string, limit = 50): Promise<{\r\n items: Array<Record<string, unknown>>\r\n nextCursor: string | null\r\n hasMore: boolean\r\n }> {\r\n const q = new URLSearchParams({ limit: String(limit) })\r\n if (cursor) q.set('cursor', cursor)\r\n const d = await this.request<{\r\n items: Array<Record<string, unknown>>\r\n next_cursor: string | null\r\n has_more: boolean\r\n }>('GET', `/conversations/${convId}/messages?${q}`)\r\n return { items: d.items ?? [], nextCursor: d.next_cursor, hasMore: d.has_more }\r\n }\r\n}\r\n","/**\r\n * Webhook signature verification per the TM IM protocol.\r\n *\r\n * Signing string = `${X-TM-Timestamp}.${rawRequestBody}`\r\n * Signature = HMAC-SHA256(webhook_secret, signing_string), hex-encoded, lowercase\r\n * Header = `X-TM-Signature: sha256=<hex>`\r\n *\r\n * The raw request body bytes MUST be used; re-serializing the parsed JSON\r\n * changes the byte layout and breaks the signature.\r\n */\r\n\r\nimport { createHmac, timingSafeEqual } from 'node:crypto'\r\n\r\n/** Hard reference-point used only by the documented self-test vector. */\r\nexport const SIGNATURE_HEADER_PREFIX = 'sha256='\r\n\r\nexport interface WebhookVerificationInput {\r\n /** Value of the `webhook_secret` issued by TM IM when WEBHOOK push mode was enabled. */\r\n secret: string\r\n /** String value of the `X-TM-Timestamp` header (Unix seconds). */\r\n timestamp: string\r\n /** Raw request body bytes, exactly as received on the wire. */\r\n rawBody: Buffer\r\n /** Value of the `X-TM-Signature` header (e.g. `sha256=abc...`). */\r\n signatureHeader: string | undefined\r\n /** Optional current time in whole seconds; defaults to `Math.floor(Date.now()/1000)`. */\r\n now?: number\r\n /** Maximum acceptable clock skew in seconds (protocol default 300). */\r\n maxSkewSeconds?: number\r\n}\r\n\r\nexport type VerificationResult =\r\n | { ok: true }\r\n | { ok: false; reason: 'missing-secret' }\r\n | { ok: false; reason: 'missing-header' }\r\n | { ok: false; reason: 'bad-format' }\r\n | { ok: false; reason: 'replay' }\r\n | { ok: false; reason: 'mismatch' }\r\n\r\n/** Constant-time HMAC-SHA256 verification helper. */\r\nexport function hmacSha256(secret: string, message: string): string {\r\n return createHmac('sha256', secret).update(message, 'utf8').digest('hex')\r\n}\r\n\r\n/** Verify a webhook request signature and freshness. Exported for reuse and tests. */\r\nexport function verifyWebhookSignature(input: WebhookVerificationInput): VerificationResult {\r\n const { secret, timestamp, rawBody, signatureHeader } = input\r\n if (!secret) return { ok: false, reason: 'missing-secret' }\r\n if (!signatureHeader) return { ok: false, reason: 'missing-header' }\r\n if (!signatureHeader.startsWith(SIGNATURE_HEADER_PREFIX)) {\r\n return { ok: false, reason: 'bad-format' }\r\n }\r\n const provided = signatureHeader.slice(SIGNATURE_HEADER_PREFIX.length)\r\n if (!/^[0-9a-f]{64}$/i.test(provided)) return { ok: false, reason: 'bad-format' }\r\n\r\n // Replay protection: reject timestamps too far from now.\r\n const ts = Number(timestamp)\r\n if (!Number.isFinite(ts)) return { ok: false, reason: 'bad-format' }\r\n const now = input.now ?? Math.floor(Date.now() / 1000)\r\n const maxSkew = input.maxSkewSeconds ?? 300\r\n if (Math.abs(ts - now) > maxSkew) return { ok: false, reason: 'replay' }\r\n\r\n const expected = hmacSha256(secret, `${timestamp}.${rawBody.toString('utf8')}`)\r\n const a = Buffer.from(provided.toLowerCase(), 'utf8')\r\n const b = Buffer.from(expected, 'utf8')\r\n const match = a.length === b.length && timingSafeEqual(a, b)\r\n return match ? { ok: true } : { ok: false, reason: 'mismatch' }\r\n}\r\n\r\n/** Parse the `X-TM-Signature` header into its raw hex digest, if well-formed. */\r\nexport function parseSignature(signatureHeader: string): string | undefined {\r\n if (!signatureHeader.startsWith(SIGNATURE_HEADER_PREFIX)) return undefined\r\n const hex = signatureHeader.slice(SIGNATURE_HEADER_PREFIX.length)\r\n return /^[0-9a-f]{64}$/i.test(hex) ? hex.toLowerCase() : undefined\r\n}\r\n","/**\r\n * TM IM webhook receiver.\r\n *\r\n * Mounts an exact HTTP route on the Harness web server that TM IM POSTs\r\n * agent-takeover events to. It:\r\n * 1. Verifies `X-TM-Signature` (HMAC-SHA256 over `ts.body`) and freshness.\r\n * 2. Deduplicates by `X-TM-Event-Id`.\r\n * 3. Returns 200 fast, then hands the event to a handler (async processing).\r\n *\r\n * Route handler owns the full response lifecycle (Harness WebServer contract).\r\n */\r\n\r\nimport type { IncomingMessage, ServerResponse } from 'node:http'\r\nimport type { Context } from '@deepseek-ai/cordis'\r\nimport type { WebRoute } from '@deepseek-ai/dsh-host-webserver'\r\nimport { verifyWebhookSignature, type VerificationResult } from './crypto.ts'\r\n\r\nexport interface WebhookEvent {\r\n event: string\r\n event_id: string\r\n occurred_at: number\r\n data: Record<string, unknown>\r\n}\r\n\r\nexport interface TmimWebhookOptions {\r\n /** Path this receiver is mounted at. Must match the endpoint_url configured in TM IM. */\r\n path: string\r\n /** webhook_secret issued by TM IM. */\r\n secret: string\r\n /** Called with each verified, non-duplicate event. Return once handed off. */\r\n handle: (event: WebhookEvent, meta: { deliveryAttempt: number }) => void | Promise<void>\r\n /** In-memory dedup set. Replace to persist across restarts. */\r\n processedEventIds?: Set<string>\r\n /** Clock skew tolerance, seconds (protocol default 300). */\r\n maxSkewSeconds?: number\r\n}\r\n\r\nfunction readBody(req: IncomingMessage): Promise<Buffer> {\r\n return new Promise((resolve, reject) => {\r\n const chunks: Buffer[] = []\r\n req.on('data', (c: Buffer) => chunks.push(c))\r\n req.on('end', () => resolve(Buffer.concat(chunks)))\r\n req.on('error', reject)\r\n })\r\n}\r\n\r\n/**\r\n * Register the webhook receiver on a Harness `webServer` context.\r\n * @returns a disposer that unmounts the route.\r\n */\r\nexport function mountTmimWebhook(ctx: Context, options: TmimWebhookOptions): () => void {\r\n const webServer = ctx.webServer\r\n const processed = options.processedEventIds ?? new Set<string>()\r\n const seen = new Map<string, string>() // event_id -> last delivery attempt\r\n\r\n const handler = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\r\n if (req.method !== 'POST') {\r\n res.writeHead(405, { 'Content-Type': 'application/json' })\r\n res.end(JSON.stringify({ error: 'method not allowed' }))\r\n return\r\n }\r\n\r\n // Capture the RAW body bytes BEFORE any parsing (signature correctness).\r\n const raw = await readBody(req)\r\n const header = (name: string) => req.headers[name.toLowerCase()]?.toString()\r\n\r\n const tsHeader = header('x-tm-timestamp')\r\n const sigHeader = header('x-tm-signature')\r\n const eventId = header('x-tm-event-id')\r\n const deliveryAttempt = Number(header('x-tm-delivery-attempt') ?? '1')\r\n\r\n const result = verifyWebhookSignature({\r\n secret: options.secret,\r\n timestamp: tsHeader ?? '',\r\n rawBody: raw,\r\n signatureHeader: sigHeader,\r\n ...(options.maxSkewSeconds === undefined ? {} : { maxSkewSeconds: options.maxSkewSeconds }),\r\n })\r\n\r\n if (!result.ok || !tsHeader) {\r\n respond(res, 400, 'unauthenticated')\r\n ctx.logger.warn(`[tmim-webhook] rejected event ${eventId ?? '(no id)'}: ${deserializeResult(result)}`)\r\n return\r\n }\r\n\r\n // Idempotency: same event_id and same-or-higher attempt -> already handled.\r\n const last = seen.get(eventId ?? '')\r\n if (eventId && last !== undefined && Number(last) <= deliveryAttempt) {\r\n respond(res, 200, 'duplicate')\r\n return\r\n }\r\n\r\n let event: WebhookEvent\r\n try {\r\n event = JSON.parse(raw.toString('utf8')) as WebhookEvent\r\n } catch {\r\n respond(res, 400, 'bad json')\r\n return\r\n }\r\n\r\n // Record before handing off; rapid retries must see \"already handled\".\r\n if (eventId) seen.set(eventId, String(deliveryAttempt))\r\n if (eventId) processed.add(eventId)\r\n\r\n respond(res, 200, 'ok')\r\n\r\n // Async processing after the fast 200 (5s limit is on HTTP return, not work).\r\n try {\r\n await options.handle(event, { deliveryAttempt })\r\n } catch (error) {\r\n ctx.logger.error(`[tmim-webhook] handler failed for event ${eventId}: ${String(error)}`)\r\n }\r\n }\r\n\r\n const dispose = webServer.register({\r\n kind: 'exact',\r\n path: options.path,\r\n handler,\r\n } satisfies WebRoute)\r\n\r\n ctx.logger.info(`[tmim-webhook] mounted at ${options.path}`)\r\n return dispose\r\n}\r\n\r\nfunction deserializeResult(result: VerificationResult): string {\r\n return result.ok ? 'ok' : result.reason\r\n}\r\n\r\nfunction respond(res: ServerResponse, status: number, statusText: string): void {\r\n res.writeHead(status, { 'Content-Type': 'application/json' })\r\n res.end(JSON.stringify({ status: statusText }))\r\n}\r\n","/**\r\n * Bridge: turn inbound TM IM events into Harness agent turns, and relay the\r\n * agent's assistant replies back to the TM IM conversation.\r\n *\r\n * One Harness session is maintained per TM IM conversation (`tmim:<convId>`).\r\n * Inbound messages become `user` messages via `agent.followup(createUserMessage(...))`.\r\n * Assistant output is observed through `session/event` (`assistant/message`) and\r\n * sent back through `TmimClient.sendText`.\r\n */\r\n\r\nimport { randomUUID } from 'node:crypto'\r\nimport type { Context } from '@deepseek-ai/cordis'\r\nimport { createUserMessage, type UserMessage } from '@deepseek-ai/dsh-llm'\r\nimport type { SessionId } from '@deepseek-ai/dsh-session'\r\nimport type { TmimClient } from './tmim-client.ts'\r\n\r\nexport interface BridgeOptions {\r\n /** Provide the authenticated TM IM client. */\r\n getClient: () => TmimClient\r\n /** Provider/model selection passed to `ctx.agents.create`. */\r\n agentOptions: { provider: string; model: string }\r\n /** Working directory for newly-created agents. */\r\n cwd?: string\r\n}\r\n\r\nexport class TmimBridge {\r\n private subscriptions: Array<() => void> = []\r\n\r\n constructor(private ctx: Context, private options: BridgeOptions) {}\r\n\r\n /** Attach listeners; returns a disposer. Call from within an effect. */\r\n start(): () => void {\r\n const onEvent = (session: { id: SessionId }, event: { type: string; data: { message?: any } }) => {\r\n if (event.type !== 'assistant/message') return\r\n const conv = this.convIdFor(session.id)\r\n if (conv === undefined) return\r\n void this.relayAssistant(conv, event)\r\n }\r\n this.subscriptions.push(this.ctx.on('session/event' as any, onEvent as any))\r\n return () => {\r\n for (const dispose of this.subscriptions.splice(0)) dispose()\r\n }\r\n }\r\n\r\n /** Accept one verified inbound tmim event (message.created / mention.created). */\r\n async ingestMessage(args: {\r\n convId: number\r\n senderId: number\r\n text?: string\r\n replyToMessageId?: number\r\n }): Promise<{ sessionId: string }> {\r\n const convId = args.convId\r\n const sessionIdStr = this.sessionIdFor(convId)\r\n let agent = this.ctx.agents.get(sessionIdStr as SessionId)\r\n if (!agent) {\r\n const handle = await this.ctx.agents.create({\r\n sessionId: sessionIdStr as SessionId,\r\n meta: { cwd: this.options.cwd ?? process.cwd() },\r\n agentOptions: this.options.agentOptions,\r\n })\r\n agent = handle.agent\r\n }\r\n const message = buildUserMessage(args.text ?? '')\r\n agent.followup(message)\r\n return { sessionId: sessionIdStr }\r\n }\r\n\r\n // ---- internals ----\r\n\r\n private sessionIdFor(convId: number): string {\r\n return `tmim:${convId}`\r\n }\r\n\r\n private convIdFor(sessionId: SessionId): number | undefined {\r\n const s = String(sessionId)\r\n if (!s.startsWith('tmim:')) return undefined\r\n const n = Number(s.slice('tmim:'.length))\r\n return Number.isFinite(n) ? n : undefined\r\n }\r\n\r\n private async relayAssistant(\r\n convId: number,\r\n event: { data: { message?: { content?: Array<{ type: string; text?: string }> } } },\r\n ): Promise<void> {\r\n const text = textOf(event.data.message)\r\n if (!text) return\r\n const client = this.options.getClient()\r\n try {\r\n await client.sendText(convId, text, newClientMsgId())\r\n this.ctx.logger.debug(`[tmim-bridge] relayed reply to conv ${convId}`)\r\n } catch (error) {\r\n this.ctx.logger.error(`[tmim-bridge] failed to relay to conv ${convId}: ${String(error)}`)\r\n }\r\n }\r\n}\r\n\r\nfunction textOf(message: { content?: Array<{ type: string; text?: string }> } | undefined): string | undefined {\r\n if (!message) return undefined\r\n return message.content\r\n ?.filter((block) => block.type === 'text' && typeof block.text === 'string')\r\n .map((block) => (block as { text: string }).text)\r\n .join('') || undefined\r\n}\r\n\r\nfunction buildUserMessage(text: string): UserMessage {\r\n return createUserMessage({\r\n content: [{ type: 'text', text }],\r\n source: { kind: 'user' },\r\n })\r\n}\r\n\r\nfunction newClientMsgId(): string {\r\n return `c-${randomUUID().replaceAll('-', '')}`\r\n}\r\n","/**\r\n * Tools exposed to the Harness agent so it can operate the TM IM network\r\n * proactively (proactive sends, conversation discovery, etc.) — independent of\r\n * the inbound webhook path.\r\n */\r\n\r\nimport type { Context } from '@deepseek-ai/cordis'\r\nimport { defineTool } from '@deepseek-ai/dsh-tools'\r\nimport type { ContentBlock } from '@deepseek-ai/dsh-llm'\r\nimport type { TmimClient } from './tmim-client.ts'\r\n\r\nfunction newClientMsgId(): string {\r\n return `c-${crypto.randomUUID().replaceAll('-', '')}`\r\n}\r\n\r\n/** Render a TM IM tool's canonical JSON result as a text card for the agent history. */\r\nfunction renderToolResult(_args: unknown, value: unknown): ContentBlock[] {\r\n return [{ type: 'text', text: JSON.stringify(value, null, 2) }]\r\n}\r\n\r\n/**\r\n * Register TM IM tools in the current context.\r\n * @returns disposer removing all registered tools.\r\n */\r\nexport function registerTmimTools(ctx: Context, client: () => TmimClient): () => void | Promise<void> {\r\n const dispose = ctx.effect(function* () {\r\n yield ctx.tools.register(defineTool({\r\n name: 'tmim_send_text',\r\n description:\r\n 'Send a text message to a TM IM conversation. Provide convId (numeric id) and text. ' +\r\n 'Use tmim_find_conversation first if you only know the peer handle.',\r\n parameters: {\r\n convId: { type: 'number', required: true, description: 'TM IM conversation id.' },\r\n text: { type: 'string', required: true, description: 'Text payload (Markdown allowed).' },\r\n replyTo: { type: 'number', description: 'Optional message_id to quote/reply to.' },\r\n },\r\n output: { schema: { type: 'json' } as const, render: renderToolResult },\r\n execute: async (args) => {\r\n const result = await client().sendText(args.convId, args.text, newClientMsgId(), args.replyTo)\r\n return { sent: true, messageId: result.messageId, seq: result.seq }\r\n },\r\n }))\r\n\r\n yield ctx.tools.register(defineTool({\r\n name: 'tmim_get_or_create_direct_conversation',\r\n description:\r\n 'Get or create a direct (1:1) conversation with a peer handle (e.g. \"@alice\"). Returns the convId.',\r\n parameters: {\r\n peer: { type: 'string', required: true, description: 'Peer handle, e.g. \"@alice\".' },\r\n },\r\n output: { schema: { type: 'json' } as const, render: renderToolResult },\r\n execute: async (args) => {\r\n const result = await client().getOrCreateDirectConversation(args.peer)\r\n return {\r\n convId: result.convId,\r\n created: result.created,\r\n peer: result.peer,\r\n lastSeq: result.lastSeq,\r\n }\r\n },\r\n }))\r\n\r\n yield ctx.tools.register(defineTool({\r\n name: 'tmim_create_group_conversation',\r\n description: 'Create a group conversation with the given title and member handles.',\r\n parameters: {\r\n title: { type: 'string', required: true, description: 'Group title.' },\r\n members: {\r\n type: 'array',\r\n required: true,\r\n items: { type: 'string' },\r\n description: 'Member handles (excluding yourself), e.g. [\"@alice\",\"@bob\"].',\r\n },\r\n },\r\n output: { schema: { type: 'json' } as const, render: renderToolResult },\r\n execute: async (args) => {\r\n const result = await client().createGroupConversation(args.title, args.members)\r\n return { convId: result.convId }\r\n },\r\n }))\r\n\r\n yield ctx.tools.register(defineTool({\r\n name: 'tmim_list_conversations',\r\n description: 'List the agent\\'s TM IM conversations (recent first).',\r\n parameters: {\r\n limit: { type: 'number', description: 'Max items (default 50).' },\r\n },\r\n output: { schema: { type: 'json' } as const, render: renderToolResult },\r\n execute: async (args) => {\r\n const result = await client().listConversations(args.limit ?? 50)\r\n return { items: result.items, nextCursor: result.nextCursor }\r\n },\r\n }))\r\n\r\n yield ctx.tools.register(defineTool({\r\n name: 'tmim_pull_messages',\r\n description: 'Incrementally pull new messages since a seq for a conversation (ascending).',\r\n parameters: {\r\n convId: { type: 'number', required: true, description: 'Conversation id.' },\r\n sinceSeq: { type: 'number', required: true, description: 'Only messages with seq > this are returned.' },\r\n limit: { type: 'number', description: 'Max items (default 200).' },\r\n },\r\n output: { schema: { type: 'json' } as const, render: renderToolResult },\r\n execute: async (args) => {\r\n const items = await client().pullMessagesSince(args.convId, args.sinceSeq, args.limit ?? 200)\r\n return { items }\r\n },\r\n }))\r\n }, 'tmim.tools')\r\n return dispose\r\n}\r\n","/**\r\n * @tmim/dsh-plugin-tmim\r\n *\r\n * DeepSeek Harness plugin that connects to the TM IM network.\r\n *\r\n * Capabilities:\r\n * - REST client for the user-end `/v1` API (send, conversations, pull).\r\n * - Agent Takeover WEBHOOK receiver (signature-verified, idempotent).\r\n * - Bridges inbound TM IM messages into Harness agent turns and relays\r\n * assistant replies back.\r\n * - Optionally registers tools so the agent can drive TM IM proactively.\r\n *\r\n * Auth modes (choose one):\r\n * - `kind: 'apikey'` + `token`: an Agent takeover api_key (Bearer). Recommended.\r\n * - `kind: 'password'` + `username` + `password`: login to get a JWT.\r\n *\r\n * Push mode:\r\n * - `webhook.enabled: true`: tmim POSTs events to `${webhook.path}` on the\r\n * Harness web server. That URL must be publicly reachable and set as the\r\n * `endpoint_url` in TM IM Agent Takeover.\r\n * - disabled: the bridge only reacts to REST tool calls; no inbound auto-turn.\r\n */\r\n\r\nimport type { Context } from '@deepseek-ai/cordis'\r\nimport z from '@deepseek-ai/schemastery'\r\nimport { TmimClient } from './tmim-client.ts'\r\nimport { mountTmimWebhook, type WebhookEvent } from './webhook.ts'\r\nimport { TmimBridge } from './bridge.ts'\r\nimport { registerTmimTools } from './tools.ts'\r\n\r\n// ---- Config ----\r\n\r\nexport interface WebhookConfig {\r\n enabled: boolean\r\n path: string\r\n secret: string\r\n maxSkewSeconds: number\r\n}\r\n\r\nexport interface AgentConfig {\r\n provider: string\r\n model: string\r\n}\r\n\r\nexport type Credential =\r\n | { kind: 'apikey'; token: string }\r\n | { kind: 'password'; username: string; password: string }\r\n\r\nexport type Config = {\r\n baseUrl: string\r\n credential: Credential\r\n deviceId: string\r\n webhook: WebhookConfig\r\n enableAgentTakeover: boolean\r\n agent: AgentConfig\r\n cwd: string\r\n tools: boolean\r\n}\r\n\r\nexport const Config = (z.object({\r\n baseUrl: z.string().required(),\r\n credential: z.union([\r\n z.object({ kind: z.const('apikey'), token: z.string().required() }),\r\n z.object({\r\n kind: z.const('password'),\r\n username: z.string().required(),\r\n password: z.string().required(),\r\n }),\r\n ]),\r\n deviceId: z.string().default(`dsh-${process.pid}`),\r\n webhook: z.object({\r\n enabled: z.boolean().default(false),\r\n path: z.string().default('/tm/callback'),\r\n secret: z.string().default(''),\r\n maxSkewSeconds: z.number().default(300),\r\n }),\r\n enableAgentTakeover: z.boolean().default(false),\r\n agent: z.object({\r\n provider: z.string().default('deepseek-official'),\r\n model: z.string().required(),\r\n }),\r\n cwd: z.string().default(process.cwd()),\r\n tools: z.boolean().default(true),\r\n}) as unknown) as Config\r\n\r\nexport const name = 'tmim'\r\n\r\nexport async function apply(ctx: Context, config: Config): Promise<void> {\r\n if (config.webhook.enabled && !config.webhook.secret) {\r\n throw new Error('[tmim] webhook.enabled requires webhook.secret (the TM IM webhook_secret)')\r\n }\r\n\r\n const client = new TmimClient({\r\n baseUrl: config.baseUrl,\r\n ...(config.credential.kind === 'apikey' ? { token: config.credential.token } : {}),\r\n deviceId: config.deviceId,\r\n })\r\n\r\n // Authenticate up-front so misconfiguration fails loudly at boot.\r\n if (config.credential.kind === 'password') {\r\n await client.login(config.credential.username, config.credential.password)\r\n ctx.logger.info('[tmim] authenticated via password login')\r\n } else {\r\n client.setToken(config.credential.token)\r\n ctx.logger.info('[tmim] using Agent api key')\r\n }\r\n\r\n // Optionally drive Agent Takeover configuration.\r\n if (config.enableAgentTakeover) {\r\n const setup = await client.configureAgentTakeover({\r\n ...(config.webhook.enabled\r\n ? { pushMode: 1 as const, endpointUrl: config.webhook.path }\r\n : { pushMode: 3 as const }), // WEBHOOK or PULL\r\n capabilities: ['text'],\r\n modelInfo: config.agent.model,\r\n })\r\n if (setup.webhookSecret && config.webhook.enabled) {\r\n ctx.logger.warn(\r\n '[tmim] NEW webhook_secret issued (shown only once) — save it and set webhook.secret. ' +\r\n 'Rotate anytime via POST /v1/me/agent-takeover/rotate-webhook-secret',\r\n )\r\n }\r\n }\r\n\r\n const bridge = new TmimBridge(ctx, {\r\n getClient: () => client,\r\n agentOptions: { provider: config.agent.provider, model: config.agent.model },\r\n cwd: config.cwd,\r\n })\r\n\r\n // Register all resources in one effect so disposal unwinds them in order.\r\n ctx.effect(function* () {\r\n if (config.webhook.enabled) {\r\n yield mountTmimWebhook(ctx, {\r\n path: config.webhook.path,\r\n secret: config.webhook.secret,\r\n maxSkewSeconds: config.webhook.maxSkewSeconds,\r\n handle: (event: WebhookEvent) => handleWebhookEvent(bridge, event),\r\n })\r\n }\r\n\r\n if (config.tools) {\r\n yield registerTmimTools(ctx, () => client)\r\n }\r\n\r\n yield bridge.start()\r\n ctx.logger.info('[tmim] started (webhook=%s, tools=%s)', config.webhook.enabled, config.tools)\r\n }, 'tmim')\r\n}\r\n\r\nfunction handleWebhookEvent(bridge: TmimBridge, event: WebhookEvent): void {\r\n if (event.event !== 'message.created' && event.event !== 'mention.created') return\r\n const data = event.data as { sender?: { actor_type?: number } } & Record<string, any>\r\n // Skip messages the agent itself sent (actor_type === 2 is an Agent).\r\n if (data.sender?.actor_type === 2) return\r\n const replyTo = Number(data.reply_to ?? 0) || undefined\r\n void bridge.ingestMessage({\r\n convId: Number(data.conv_id),\r\n senderId: Number(data.sender_id),\r\n text: data.content?.text,\r\n ...(replyTo === undefined ? {} : { replyToMessageId: replyTo }),\r\n })\r\n}\r\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["newClientMsgId"],"sources":["../src/tmim-client.ts","../src/crypto.ts","../src/webhook.ts","../src/bridge.ts","../src/tools.ts","../src/index.ts"],"sourcesContent":["/**\r\n * Minimal TM IM REST client (user-end `/v1`) for a Harness agent.\r\n *\r\n * Implements the endpoints a connector needs:\r\n * - auth (login / refresh / agent takeover api key)\r\n * - direct conversation get-or-create\r\n * - group conversation create\r\n * - list conversations\r\n * - send a text message (with idempotent client_msg_id)\r\n * - incremental pull (since_seq) and cursor pull\r\n * - agent takeover configuration (auth + status + enable/disable WEBHOOK)\r\n *\r\n * All requests expect the TM IM unified envelope:\r\n * { \"code\": 0, \"message\": \"ok\", \"data\": {...} }\r\n * where a non-zero `code` is a business error returned over HTTP 200.\r\n */\r\n\r\nexport interface TmimConfig {\r\n /** Base URL, e.g. `https://tmim.example.com`. Do NOT append `/v1`. */\r\n baseUrl: string\r\n /** Authentication: either an Agent takeover api_key (`Bearer`) ... */\r\n token?: string\r\n /** ... or credentials used to log in and obtain a JWT. */\r\n username?: string\r\n password?: string\r\n /** Stable device id reported via `X-TM-Device-Id`. */\r\n deviceId?: string\r\n /** Custom fetch; defaults to global fetch. */\r\n fetch?: typeof fetch\r\n /** Timeout for each HTTP request, ms. */\r\n timeoutMs?: number\r\n}\r\n\r\ninterface TmimEnvelope<T> {\r\n code: number\r\n message: string\r\n data: T | null\r\n}\r\n\r\nexport class TmimError extends Error {\r\n constructor(\r\n /** Business code from the envelope. 0 means transport-level success. */\r\n public readonly code: number,\r\n message: string,\r\n public readonly httpStatus?: number,\r\n public readonly payload?: unknown,\r\n ) {\r\n super(message)\r\n this.name = 'TmimError'\r\n }\r\n}\r\n\r\nexport class TmimClient {\r\n private token: string | undefined\r\n private fetchImpl: typeof fetch\r\n private timeoutMs: number\r\n\r\n constructor(private readonly config: TmimConfig) {\r\n this.token = config.token\r\n this.fetchImpl = config.fetch ?? globalThis.fetch.bind(globalThis)\r\n this.timeoutMs = config.timeoutMs ?? 15_000\r\n }\r\n\r\n /** Current bearer token (may be a JWT or an Agent api_key). */\r\n get authToken(): string | undefined {\r\n return this.token\r\n }\r\n\r\n // ---- low level ----\r\n\r\n private async request<T>(\r\n method: string,\r\n path: string,\r\n body?: unknown,\r\n opts: { auth?: boolean; headers?: Record<string, string> } = {},\r\n ): Promise<T> {\r\n const url = `${this.config.baseUrl}/v1${path}`\r\n const headers: Record<string, string> = {\r\n Accept: 'application/json',\r\n ...(opts.headers ?? {}),\r\n }\r\n if (body !== undefined) headers['Content-Type'] = 'application/json; charset=utf-8'\r\n if (this.config.deviceId) headers['X-TM-Device-Id'] = this.config.deviceId\r\n if (opts.auth !== false && this.token) headers['Authorization'] = `Bearer ${this.token}`\r\n\r\n const controller = new AbortController()\r\n const timer = setTimeout(() => controller.abort(), this.timeoutMs)\r\n try {\r\n const res = await this.fetchImpl(url, {\r\n method,\r\n headers,\r\n ...(body === undefined ? {} : { body: JSON.stringify(body) }),\r\n signal: controller.signal,\r\n })\r\n let envelope: TmimEnvelope<T> | undefined\r\n let text = ''\r\n try {\r\n text = await res.text()\r\n envelope = JSON.parse(text) as TmimEnvelope<T>\r\n } catch {\r\n envelope = undefined\r\n }\r\n\r\n if (res.status === 401 || res.status === 403) {\r\n throw new TmimError(res.status, `tmim auth failed (HTTP ${res.status})`, res.status, text)\r\n }\r\n if (!res.ok && !envelope) {\r\n throw new TmimError(0, `tmim HTTP ${res.status}: ${text.slice(0, 300)}`, res.status, text)\r\n }\r\n if (envelope && envelope.code !== 0) {\r\n throw new TmimError(envelope.code, envelope.message ?? `tmim error ${envelope.code}`, res.status, envelope)\r\n }\r\n return envelope?.data ?? (undefined as T)\r\n } finally {\r\n clearTimeout(timer)\r\n }\r\n }\r\n\r\n // ---- auth ----\r\n\r\n /** Login with handle/password; stores the returned JWT. */\r\n async login(handle: string, password: string): Promise<{ token: string; refreshToken: string }> {\r\n const data = await this.request<{\r\n token: string\r\n refresh_token: string\r\n actor: unknown\r\n }>('POST', '/auth/login', { handle, password }, { auth: false })\r\n this.token = data.token\r\n return { token: data.token, refreshToken: data.refresh_token }\r\n }\r\n\r\n /** Exchange a refresh token for a new access token (refresh_token is one-time). */\r\n async refresh(refreshToken: string): Promise<{ token: string; refreshToken: string }> {\r\n const data = await this.request<{ token: string; refresh_token: string }>(\r\n 'POST',\r\n '/auth/refresh',\r\n { refresh_token: refreshToken },\r\n { auth: false },\r\n )\r\n this.token = data.token\r\n return { token: data.token, refreshToken: data.refresh_token }\r\n }\r\n\r\n /** Set an explicit bearer token (e.g. an Agent takeover api_key). */\r\n setToken(token: string): this {\r\n this.token = token\r\n return this\r\n }\r\n\r\n // ---- agent takeover ----\r\n\r\n /**\r\n * Configure Agent Takeover.\r\n * doc: POST /v1/me/agent-takeover\r\n * Returns the issued webhook_secret ONLY when the merged push mode is\r\n * WEBHOOK and no secret existed yet (shown exactly once).\r\n */\r\n async configureAgentTakeover(args: {\r\n pushMode: 1 | 2 | 3 // 1=WEBHOOK, 2=LONG_POLL, 3=PULL\r\n endpointUrl?: string\r\n capabilities?: string[]\r\n modelInfo?: string\r\n rateLimit?: number\r\n }): Promise<{ webhookSecret?: string }> {\r\n const body: Record<string, unknown> = { push_mode: args.pushMode }\r\n if (args.endpointUrl !== undefined) body.endpoint_url = args.endpointUrl\r\n if (args.capabilities !== undefined) body.capabilities = args.capabilities\r\n if (args.modelInfo !== undefined) body.model_info = args.modelInfo\r\n if (args.rateLimit !== undefined) body.rate_limit = args.rateLimit\r\n return this.request<{ webhookSecret?: string }>('POST', '/me/agent-takeover', body)\r\n }\r\n\r\n /** Rotate the webhook secret; returns the new plaintext exactly once. */\r\n async rotateWebhookSecret(): Promise<{ webhookSecret: string }> {\r\n return this.request<{ webhookSecret: string }>(\r\n 'POST',\r\n '/me/agent-takeover/rotate-webhook-secret',\r\n )\r\n }\r\n\r\n /** Disable Agent Takeover entirely. */\r\n async disableAgentTakeover(): Promise<void> {\r\n await this.request<null>('DELETE', '/me/agent-takeover')\r\n }\r\n\r\n /** Read current takeover config (never includes secrets). */\r\n async getAgentTakeoverStatus(): Promise<{\r\n actorId: number\r\n enabled: boolean\r\n pushMode: number\r\n endpointUrl?: string\r\n hasApiKey: boolean\r\n hasWebhookSecret: boolean\r\n }> {\r\n const d = await this.request<{\r\n actor_id: number\r\n enabled: boolean\r\n push_mode: number\r\n endpoint_url?: string\r\n has_api_key: boolean\r\n has_webhook_secret: boolean\r\n }>('GET', '/me/agent-takeover')\r\n return {\r\n actorId: d.actor_id,\r\n enabled: d.enabled,\r\n pushMode: d.push_mode,\r\n ...(d.endpoint_url === undefined ? {} : { endpointUrl: d.endpoint_url }),\r\n hasApiKey: d.has_api_key,\r\n hasWebhookSecret: d.has_webhook_secret,\r\n }\r\n }\r\n\r\n // ---- conversations ----\r\n\r\n /** Get or create a direct (1:1) conversation with a peer. */\r\n async getOrCreateDirectConversation(peer: string): Promise<{\r\n convId: number\r\n convType: number\r\n peer: { actor_id: number; handle: string; display_name: string; online: boolean }\r\n created: boolean\r\n lastSeq: number\r\n }> {\r\n const d = await this.request<{\r\n conv_id: number\r\n conv_type: number\r\n peer: { actor_id: number; handle: string; display_name: string; online: boolean }\r\n created: boolean\r\n last_seq: number\r\n }>('POST', '/conversations/direct', { peer })\r\n return {\r\n convId: d.conv_id,\r\n convType: d.conv_type,\r\n peer: d.peer,\r\n created: d.created,\r\n lastSeq: d.last_seq,\r\n }\r\n }\r\n\r\n /** Create a group conversation. `members` excludes the caller. */\r\n async createGroupConversation(title: string, members: string[]): Promise<{ convId: number }> {\r\n const d = await this.request<{ conv_id: number }>('POST', '/conversations/group', { title, members })\r\n return { convId: d.conv_id }\r\n }\r\n\r\n /** List the caller's conversations (cursor paginated). */\r\n async listConversations(limit = 50, cursor?: string): Promise<{\r\n items: Array<{ conv_id: number; conv_type: number; title: string | null; last_seq: number }>\r\n nextCursor: string | null\r\n }> {\r\n const q = new URLSearchParams({ limit: String(limit) })\r\n if (cursor) q.set('cursor', cursor)\r\n const d = await this.request<{ items: Array<{ conv_id: number } & Record<string, unknown>>; next_cursor: string | null }>('GET', `/conversations?${q}`)\r\n return { items: d.items as any, nextCursor: d.next_cursor }\r\n }\r\n\r\n // ---- messages ----\r\n\r\n /**\r\n * Send a text message. `clientMsgId` is the idempotency key; re-sending the\r\n * same key returns the identical original message (no duplicate).\r\n */\r\n async sendText(convId: number, text: string, clientMsgId: string, replyTo?: number): Promise<{\r\n messageId: number\r\n convId: number\r\n seq: number\r\n clientMsgId: string\r\n senderId: number\r\n createdAt: string\r\n }> {\r\n const d = await this.request<{\r\n message_id: number\r\n conv_id: number\r\n seq: number\r\n client_msg_id: string\r\n sender_id: number\r\n created_at: string\r\n }>('POST', `/conversations/${convId}/messages`, {\r\n msg_type: 'TEXT',\r\n client_msg_id: clientMsgId,\r\n content: { text },\r\n ...(replyTo === undefined ? {} : { reply_to: replyTo }),\r\n })\r\n return {\r\n messageId: d.message_id,\r\n convId: d.conv_id,\r\n seq: d.seq,\r\n clientMsgId: d.client_msg_id,\r\n senderId: d.sender_id,\r\n createdAt: d.created_at,\r\n }\r\n }\r\n\r\n /** Incremental pull: messages with seq > sinceSeq, ascending. */\r\n async pullMessagesSince(convId: number, sinceSeq: number, limit = 200): Promise<Array<{\r\n message_id: number\r\n conv_id: number\r\n seq: number\r\n sender_id: number\r\n msg_type: string\r\n content: { text?: string }\r\n created_at: string\r\n }>> {\r\n const q = new URLSearchParams({ since_seq: String(sinceSeq), limit: String(limit) })\r\n const d = await this.request<{ items: Array<any> }>('GET', `/conversations/${convId}/messages?${q}`)\r\n return d.items ?? []\r\n }\r\n\r\n /** Cursor pull: newest-first history page. */\r\n async pullConversationHistory(convId: number, cursor?: string, limit = 50): Promise<{\r\n items: Array<Record<string, unknown>>\r\n nextCursor: string | null\r\n hasMore: boolean\r\n }> {\r\n const q = new URLSearchParams({ limit: String(limit) })\r\n if (cursor) q.set('cursor', cursor)\r\n const d = await this.request<{\r\n items: Array<Record<string, unknown>>\r\n next_cursor: string | null\r\n has_more: boolean\r\n }>('GET', `/conversations/${convId}/messages?${q}`)\r\n return { items: d.items ?? [], nextCursor: d.next_cursor, hasMore: d.has_more }\r\n }\r\n}\r\n","/**\r\n * Webhook signature verification per the TM IM protocol.\r\n *\r\n * Signing string = `${X-TM-Timestamp}.${rawRequestBody}`\r\n * Signature = HMAC-SHA256(webhook_secret, signing_string), hex-encoded, lowercase\r\n * Header = `X-TM-Signature: sha256=<hex>`\r\n *\r\n * The raw request body bytes MUST be used; re-serializing the parsed JSON\r\n * changes the byte layout and breaks the signature.\r\n */\r\n\r\nimport { createHmac, timingSafeEqual } from 'node:crypto'\r\n\r\n/** Hard reference-point used only by the documented self-test vector. */\r\nexport const SIGNATURE_HEADER_PREFIX = 'sha256='\r\n\r\nexport interface WebhookVerificationInput {\r\n /** Value of the `webhook_secret` issued by TM IM when WEBHOOK push mode was enabled. */\r\n secret: string\r\n /** String value of the `X-TM-Timestamp` header (Unix seconds). */\r\n timestamp: string\r\n /** Raw request body bytes, exactly as received on the wire. */\r\n rawBody: Buffer\r\n /** Value of the `X-TM-Signature` header (e.g. `sha256=abc...`). */\r\n signatureHeader: string | undefined\r\n /** Optional current time in whole seconds; defaults to `Math.floor(Date.now()/1000)`. */\r\n now?: number\r\n /** Maximum acceptable clock skew in seconds (protocol default 300). */\r\n maxSkewSeconds?: number\r\n}\r\n\r\nexport type VerificationResult =\r\n | { ok: true }\r\n | { ok: false; reason: 'missing-secret' }\r\n | { ok: false; reason: 'missing-header' }\r\n | { ok: false; reason: 'bad-format' }\r\n | { ok: false; reason: 'replay' }\r\n | { ok: false; reason: 'mismatch' }\r\n\r\n/** Constant-time HMAC-SHA256 verification helper. */\r\nexport function hmacSha256(secret: string, message: string): string {\r\n return createHmac('sha256', secret).update(message, 'utf8').digest('hex')\r\n}\r\n\r\n/** Verify a webhook request signature and freshness. Exported for reuse and tests. */\r\nexport function verifyWebhookSignature(input: WebhookVerificationInput): VerificationResult {\r\n const { secret, timestamp, rawBody, signatureHeader } = input\r\n if (!secret) return { ok: false, reason: 'missing-secret' }\r\n if (!signatureHeader) return { ok: false, reason: 'missing-header' }\r\n if (!signatureHeader.startsWith(SIGNATURE_HEADER_PREFIX)) {\r\n return { ok: false, reason: 'bad-format' }\r\n }\r\n const provided = signatureHeader.slice(SIGNATURE_HEADER_PREFIX.length)\r\n if (!/^[0-9a-f]{64}$/i.test(provided)) return { ok: false, reason: 'bad-format' }\r\n\r\n // Replay protection: reject timestamps too far from now.\r\n const ts = Number(timestamp)\r\n if (!Number.isFinite(ts)) return { ok: false, reason: 'bad-format' }\r\n const now = input.now ?? Math.floor(Date.now() / 1000)\r\n const maxSkew = input.maxSkewSeconds ?? 300\r\n if (Math.abs(ts - now) > maxSkew) return { ok: false, reason: 'replay' }\r\n\r\n const expected = hmacSha256(secret, `${timestamp}.${rawBody.toString('utf8')}`)\r\n const a = Buffer.from(provided.toLowerCase(), 'utf8')\r\n const b = Buffer.from(expected, 'utf8')\r\n const match = a.length === b.length && timingSafeEqual(a, b)\r\n return match ? { ok: true } : { ok: false, reason: 'mismatch' }\r\n}\r\n\r\n/** Parse the `X-TM-Signature` header into its raw hex digest, if well-formed. */\r\nexport function parseSignature(signatureHeader: string): string | undefined {\r\n if (!signatureHeader.startsWith(SIGNATURE_HEADER_PREFIX)) return undefined\r\n const hex = signatureHeader.slice(SIGNATURE_HEADER_PREFIX.length)\r\n return /^[0-9a-f]{64}$/i.test(hex) ? hex.toLowerCase() : undefined\r\n}\r\n","/**\r\n * TM IM webhook receiver.\r\n *\r\n * Mounts an exact HTTP route on the Harness web server that TM IM POSTs\r\n * agent-takeover events to. It:\r\n * 1. Verifies `X-TM-Signature` (HMAC-SHA256 over `ts.body`) and freshness.\r\n * 2. Deduplicates by `X-TM-Event-Id`.\r\n * 3. Returns 200 fast, then hands the event to a handler (async processing).\r\n *\r\n * Route handler owns the full response lifecycle (Harness WebServer contract).\r\n */\r\n\r\nimport type { IncomingMessage, ServerResponse } from 'node:http'\r\nimport type { Context } from '@deepseek-ai/cordis'\r\nimport type { WebRoute } from '@deepseek-ai/dsh-host-webserver'\r\nimport { verifyWebhookSignature, type VerificationResult } from './crypto.ts'\r\n\r\nexport interface WebhookEvent {\r\n event: string\r\n event_id: string\r\n occurred_at: number\r\n data: Record<string, unknown>\r\n}\r\n\r\nexport interface TmimWebhookOptions {\r\n /** Path this receiver is mounted at. Must match the endpoint_url configured in TM IM. */\r\n path: string\r\n /** webhook_secret issued by TM IM. */\r\n secret: string\r\n /** Called with each verified, non-duplicate event. Return once handed off. */\r\n handle: (event: WebhookEvent, meta: { deliveryAttempt: number }) => void | Promise<void>\r\n /** In-memory dedup set. Replace to persist across restarts. */\r\n processedEventIds?: Set<string>\r\n /** Clock skew tolerance, seconds (protocol default 300). */\r\n maxSkewSeconds?: number\r\n}\r\n\r\nfunction readBody(req: IncomingMessage): Promise<Buffer> {\r\n return new Promise((resolve, reject) => {\r\n const chunks: Buffer[] = []\r\n req.on('data', (c: Buffer) => chunks.push(c))\r\n req.on('end', () => resolve(Buffer.concat(chunks)))\r\n req.on('error', reject)\r\n })\r\n}\r\n\r\n/**\r\n * Register the webhook receiver on a Harness `webServer` context.\r\n * @returns a disposer that unmounts the route.\r\n */\r\nexport function mountTmimWebhook(ctx: Context, options: TmimWebhookOptions): () => void {\r\n const webServer = ctx.webServer\r\n const processed = options.processedEventIds ?? new Set<string>()\r\n const seen = new Map<string, string>() // event_id -> last delivery attempt\r\n\r\n const handler = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\r\n if (req.method !== 'POST') {\r\n res.writeHead(405, { 'Content-Type': 'application/json' })\r\n res.end(JSON.stringify({ error: 'method not allowed' }))\r\n return\r\n }\r\n\r\n // Capture the RAW body bytes BEFORE any parsing (signature correctness).\r\n const raw = await readBody(req)\r\n const header = (name: string) => req.headers[name.toLowerCase()]?.toString()\r\n\r\n const tsHeader = header('x-tm-timestamp')\r\n const sigHeader = header('x-tm-signature')\r\n const eventId = header('x-tm-event-id')\r\n const deliveryAttempt = Number(header('x-tm-delivery-attempt') ?? '1')\r\n\r\n const result = verifyWebhookSignature({\r\n secret: options.secret,\r\n timestamp: tsHeader ?? '',\r\n rawBody: raw,\r\n signatureHeader: sigHeader,\r\n ...(options.maxSkewSeconds === undefined ? {} : { maxSkewSeconds: options.maxSkewSeconds }),\r\n })\r\n\r\n if (!result.ok || !tsHeader) {\r\n respond(res, 400, 'unauthenticated')\r\n ctx.logger.warn(`[tmim-webhook] rejected event ${eventId ?? '(no id)'}: ${deserializeResult(result)}`)\r\n return\r\n }\r\n\r\n // Idempotency: same event_id and same-or-higher attempt -> already handled.\r\n const last = seen.get(eventId ?? '')\r\n if (eventId && last !== undefined && Number(last) <= deliveryAttempt) {\r\n respond(res, 200, 'duplicate')\r\n return\r\n }\r\n\r\n let event: WebhookEvent\r\n try {\r\n event = JSON.parse(raw.toString('utf8')) as WebhookEvent\r\n } catch {\r\n respond(res, 400, 'bad json')\r\n return\r\n }\r\n\r\n // Record before handing off; rapid retries must see \"already handled\".\r\n if (eventId) seen.set(eventId, String(deliveryAttempt))\r\n if (eventId) processed.add(eventId)\r\n\r\n respond(res, 200, 'ok')\r\n\r\n // Async processing after the fast 200 (5s limit is on HTTP return, not work).\r\n try {\r\n await options.handle(event, { deliveryAttempt })\r\n } catch (error) {\r\n ctx.logger.error(`[tmim-webhook] handler failed for event ${eventId}: ${String(error)}`)\r\n }\r\n }\r\n\r\n const dispose = webServer.register({\r\n kind: 'exact',\r\n path: options.path,\r\n handler,\r\n } satisfies WebRoute)\r\n\r\n ctx.logger.info(`[tmim-webhook] mounted at ${options.path}`)\r\n return dispose\r\n}\r\n\r\nfunction deserializeResult(result: VerificationResult): string {\r\n return result.ok ? 'ok' : result.reason\r\n}\r\n\r\nfunction respond(res: ServerResponse, status: number, statusText: string): void {\r\n res.writeHead(status, { 'Content-Type': 'application/json' })\r\n res.end(JSON.stringify({ status: statusText }))\r\n}\r\n","/**\r\n * Bridge: turn inbound TM IM events into Harness agent turns, and relay the\r\n * agent's assistant replies back to the TM IM conversation.\r\n *\r\n * One Harness session is maintained per TM IM conversation (`tmim:<convId>`).\r\n * Inbound messages become `user` messages via `agent.followup(createUserMessage(...))`.\r\n * Assistant output is observed through `session/event` (`assistant/message`) and\r\n * sent back through `TmimClient.sendText`.\r\n */\r\n\r\nimport { randomUUID } from 'node:crypto'\r\nimport type { Context } from '@deepseek-ai/cordis'\r\nimport { createUserMessage, type UserMessage } from '@deepseek-ai/dsh-llm'\r\nimport type { SessionId } from '@deepseek-ai/dsh-session'\r\nimport type { TmimClient } from './tmim-client.ts'\r\n\r\nexport interface BridgeOptions {\r\n /** Provide the authenticated TM IM client. */\r\n getClient: () => TmimClient\r\n /** Provider/model selection passed to `ctx.agents.create`. */\r\n agentOptions: { provider: string; model: string }\r\n /** Working directory for newly-created agents. */\r\n cwd?: string\r\n}\r\n\r\nexport class TmimBridge {\r\n private subscriptions: Array<() => void> = []\r\n\r\n constructor(private ctx: Context, private options: BridgeOptions) {}\r\n\r\n /** Attach listeners; returns a disposer. Call from within an effect. */\r\n start(): () => void {\r\n const onEvent = (session: { id: SessionId }, event: { type: string; data: { message?: any } }) => {\r\n if (event.type !== 'assistant/message') return\r\n const conv = this.convIdFor(session.id)\r\n if (conv === undefined) return\r\n void this.relayAssistant(conv, event)\r\n }\r\n this.subscriptions.push(this.ctx.on('session/event' as any, onEvent as any))\r\n return () => {\r\n for (const dispose of this.subscriptions.splice(0)) dispose()\r\n }\r\n }\r\n\r\n /** Accept one verified inbound tmim event (message.created / mention.created). */\r\n async ingestMessage(args: {\r\n convId: number\r\n senderId: number\r\n text?: string\r\n replyToMessageId?: number\r\n }): Promise<{ sessionId: string }> {\r\n const convId = args.convId\r\n const sessionIdStr = this.sessionIdFor(convId)\r\n let agent = this.ctx.agents.get(sessionIdStr as SessionId)\r\n if (!agent) {\r\n const handle = await this.ctx.agents.create({\r\n sessionId: sessionIdStr as SessionId,\r\n meta: { cwd: this.options.cwd ?? process.cwd() },\r\n agentOptions: this.options.agentOptions,\r\n })\r\n agent = handle.agent\r\n }\r\n const message = buildUserMessage(args.text ?? '')\r\n agent.followup(message)\r\n return { sessionId: sessionIdStr }\r\n }\r\n\r\n // ---- internals ----\r\n\r\n private sessionIdFor(convId: number): string {\r\n return `tmim:${convId}`\r\n }\r\n\r\n private convIdFor(sessionId: SessionId): number | undefined {\r\n const s = String(sessionId)\r\n if (!s.startsWith('tmim:')) return undefined\r\n const n = Number(s.slice('tmim:'.length))\r\n return Number.isFinite(n) ? n : undefined\r\n }\r\n\r\n private async relayAssistant(\r\n convId: number,\r\n event: { data: { message?: { content?: Array<{ type: string; text?: string }> } } },\r\n ): Promise<void> {\r\n const text = textOf(event.data.message)\r\n if (!text) return\r\n const client = this.options.getClient()\r\n try {\r\n await client.sendText(convId, text, newClientMsgId())\r\n this.ctx.logger.debug(`[tmim-bridge] relayed reply to conv ${convId}`)\r\n } catch (error) {\r\n this.ctx.logger.error(`[tmim-bridge] failed to relay to conv ${convId}: ${String(error)}`)\r\n }\r\n }\r\n}\r\n\r\nfunction textOf(message: { content?: Array<{ type: string; text?: string }> } | undefined): string | undefined {\r\n if (!message) return undefined\r\n return message.content\r\n ?.filter((block) => block.type === 'text' && typeof block.text === 'string')\r\n .map((block) => (block as { text: string }).text)\r\n .join('') || undefined\r\n}\r\n\r\nfunction buildUserMessage(text: string): UserMessage {\r\n return createUserMessage({\r\n content: [{ type: 'text', text }],\r\n source: { kind: 'user' },\r\n })\r\n}\r\n\r\nfunction newClientMsgId(): string {\r\n return `c-${randomUUID().replaceAll('-', '')}`\r\n}\r\n","/**\r\n * Tools exposed to the Harness agent so it can operate the TM IM network\r\n * proactively (proactive sends, conversation discovery, etc.) — independent of\r\n * the inbound webhook path.\r\n */\r\n\r\nimport type { Context } from '@deepseek-ai/cordis'\r\nimport { defineTool } from '@deepseek-ai/dsh-tools'\r\nimport type { ContentBlock } from '@deepseek-ai/dsh-llm'\r\nimport type { TmimClient } from './tmim-client.ts'\r\n\r\nfunction newClientMsgId(): string {\r\n return `c-${crypto.randomUUID().replaceAll('-', '')}`\r\n}\r\n\r\n/** Render a TM IM tool's canonical JSON result as a text card for the agent history. */\r\nfunction renderToolResult(_args: unknown, value: unknown): ContentBlock[] {\r\n return [{ type: 'text', text: JSON.stringify(value, null, 2) }]\r\n}\r\n\r\n/**\r\n * Register TM IM tools in the current context.\r\n * @returns disposer removing all registered tools.\r\n */\r\nexport function registerTmimTools(ctx: Context, client: () => TmimClient): () => void | Promise<void> {\r\n const dispose = ctx.effect(function* () {\r\n yield ctx.tools.register(defineTool({\r\n name: 'tmim_send_text',\r\n description:\r\n 'Send a text message to a TM IM conversation. Provide convId (numeric id) and text. ' +\r\n 'Use tmim_find_conversation first if you only know the peer handle.',\r\n parameters: {\r\n convId: { type: 'number', required: true, description: 'TM IM conversation id.' },\r\n text: { type: 'string', required: true, description: 'Text payload (Markdown allowed).' },\r\n replyTo: { type: 'number', description: 'Optional message_id to quote/reply to.' },\r\n },\r\n output: { schema: { type: 'json' } as const, render: renderToolResult },\r\n execute: async (args) => {\r\n const result = await client().sendText(args.convId, args.text, newClientMsgId(), args.replyTo)\r\n return { sent: true, messageId: result.messageId, seq: result.seq }\r\n },\r\n }))\r\n\r\n yield ctx.tools.register(defineTool({\r\n name: 'tmim_get_or_create_direct_conversation',\r\n description:\r\n 'Get or create a direct (1:1) conversation with a peer handle (e.g. \"@alice\"). Returns the convId.',\r\n parameters: {\r\n peer: { type: 'string', required: true, description: 'Peer handle, e.g. \"@alice\".' },\r\n },\r\n output: { schema: { type: 'json' } as const, render: renderToolResult },\r\n execute: async (args) => {\r\n const result = await client().getOrCreateDirectConversation(args.peer)\r\n return {\r\n convId: result.convId,\r\n created: result.created,\r\n peer: result.peer,\r\n lastSeq: result.lastSeq,\r\n }\r\n },\r\n }))\r\n\r\n yield ctx.tools.register(defineTool({\r\n name: 'tmim_create_group_conversation',\r\n description: 'Create a group conversation with the given title and member handles.',\r\n parameters: {\r\n title: { type: 'string', required: true, description: 'Group title.' },\r\n members: {\r\n type: 'array',\r\n required: true,\r\n items: { type: 'string' },\r\n description: 'Member handles (excluding yourself), e.g. [\"@alice\",\"@bob\"].',\r\n },\r\n },\r\n output: { schema: { type: 'json' } as const, render: renderToolResult },\r\n execute: async (args) => {\r\n const result = await client().createGroupConversation(args.title, args.members)\r\n return { convId: result.convId }\r\n },\r\n }))\r\n\r\n yield ctx.tools.register(defineTool({\r\n name: 'tmim_list_conversations',\r\n description: 'List the agent\\'s TM IM conversations (recent first).',\r\n parameters: {\r\n limit: { type: 'number', description: 'Max items (default 50).' },\r\n },\r\n output: { schema: { type: 'json' } as const, render: renderToolResult },\r\n execute: async (args) => {\r\n const result = await client().listConversations(args.limit ?? 50)\r\n return { items: result.items, nextCursor: result.nextCursor }\r\n },\r\n }))\r\n\r\n yield ctx.tools.register(defineTool({\r\n name: 'tmim_pull_messages',\r\n description: 'Incrementally pull new messages since a seq for a conversation (ascending).',\r\n parameters: {\r\n convId: { type: 'number', required: true, description: 'Conversation id.' },\r\n sinceSeq: { type: 'number', required: true, description: 'Only messages with seq > this are returned.' },\r\n limit: { type: 'number', description: 'Max items (default 200).' },\r\n },\r\n output: { schema: { type: 'json' } as const, render: renderToolResult },\r\n execute: async (args) => {\r\n const items = await client().pullMessagesSince(args.convId, args.sinceSeq, args.limit ?? 200)\r\n return { items }\r\n },\r\n }))\r\n }, 'tmim.tools')\r\n return dispose\r\n}\r\n","/**\r\n * @tmim/dsh-plugin-tmim\r\n *\r\n * DeepSeek Harness plugin that connects to the TM IM network.\r\n *\r\n * Capabilities:\r\n * - REST client for the user-end `/v1` API (send, conversations, pull).\r\n * - Agent Takeover WEBHOOK receiver (signature-verified, idempotent).\r\n * - Bridges inbound TM IM messages into Harness agent turns and relays\r\n * assistant replies back.\r\n * - Optionally registers tools so the agent can drive TM IM proactively.\r\n *\r\n * Auth modes (choose one):\r\n * - `kind: 'apikey'` + `token`: an Agent takeover api_key (Bearer). Recommended.\r\n * - `kind: 'password'` + `username` + `password`: login to get a JWT.\r\n *\r\n * Push mode:\r\n * - `webhook.enabled: true`: tmim POSTs events to `${webhook.path}` on the\r\n * Harness web server. That URL must be publicly reachable and set as the\r\n * `endpoint_url` in TM IM Agent Takeover.\r\n * - disabled: the bridge only reacts to REST tool calls; no inbound auto-turn.\r\n */\r\n\r\nimport type { Context } from '@deepseek-ai/cordis'\r\nimport z from '@deepseek-ai/schemastery'\r\nimport { TmimClient } from './tmim-client.ts'\r\nimport { mountTmimWebhook, type WebhookEvent } from './webhook.ts'\r\nimport { TmimBridge } from './bridge.ts'\r\nimport { registerTmimTools } from './tools.ts'\r\n\r\n// ---- Config ----\r\n\r\nexport interface WebhookConfig {\r\n enabled: boolean\r\n path: string\r\n secret: string\r\n maxSkewSeconds: number\r\n}\r\n\r\nexport interface AgentConfig {\r\n provider: string\r\n model: string\r\n}\r\n\r\nexport type Credential =\r\n | { kind: 'apikey'; token: string }\r\n | { kind: 'password'; username: string; password: string }\r\n\r\nexport type Config = {\r\n baseUrl: string\r\n /** Optional until a TM IM connection is actually needed. */\r\n credential?: Credential\r\n deviceId: string\r\n webhook: WebhookConfig\r\n enableAgentTakeover: boolean\r\n agent: AgentConfig\r\n cwd: string\r\n tools: boolean\r\n}\r\n\r\nexport const Config = (z.object({\r\n baseUrl: z.string().default('http://tmim.stkg.ltd/').description(\r\n 'TM IM 服务器地址(不带 /v1 后缀)。例如:https://tmim.example.com',\r\n ),\r\n credential: z.union([\r\n z.object({\r\n kind: z.const('apikey').description('使用 Agent Takeover 的 api_key(sk_… 开头,长期有效)'),\r\n token: z.string().role('secret').required().description('Agent Takeover api_key,在 TM IM 的「Agent 接管」页开启时获取'),\r\n }),\r\n z.object({\r\n kind: z.const('password').description('使用 TM IM 登录账号和密码'),\r\n username: z.string().required().description('TM IM 登录账号'),\r\n password: z.string().role('secret').required().description('TM IM 登录密码'),\r\n }),\r\n ]).default({ kind: 'apikey', token: '' } as const).description('TM IM 连接凭证,可选;缺省时插件仍会启动,但只有配置凭证后才能收发消息'),\r\n deviceId: z.string().default(`dsh-${process.pid}`).description('设备标识,会随请求上报(X-TM-Device-Id)'),\r\n webhook: z.object({\r\n enabled: z.boolean().default(false).description('是否开启 WEBHOOK 实时接收消息(需要公网可达的 HTTPS endpoint)'),\r\n path: z.string().default('/tm/callback').description('WEBHOOK 回调路径;对应 TM IM 的 endpoint_url'),\r\n secret: z.string().role('secret').default('').description('WEBHOOK 签名密钥(webhook_secret),启用时必填;由 TM IM 签发'),\r\n maxSkewSeconds: z.number().default(300).description('防重放的签名时间戳容忍偏差上限(秒)'),\r\n }),\r\n enableAgentTakeover: z.boolean().default(false).description('启动时是否自动 POST 开启 Agent Takeover 配置(需要已配置凭证)'),\r\n agent: z.object({\r\n provider: z.string().default('deepseek-official').description('应答所用的模型提供商'),\r\n model: z.string().default('deepseek-chat').description('应答所用的模型名,如 deepseek-chat'),\r\n }),\r\n cwd: z.string().default(process.cwd()).description('工作目录'),\r\n tools: z.boolean().default(true).description('是否向智能体注册主动式的 TM IM 工具(发消息/建会话等)'),\r\n}) as unknown) as Config\r\n\r\nexport const name = 'tmim'\r\n\r\nexport async function apply(ctx: Context, config: Config): Promise<void> {\r\n if (config.webhook.enabled && !config.webhook.secret) {\r\n throw new Error('[tmim] webhook.enabled requires webhook.secret (the TM IM webhook_secret)')\r\n }\r\n\r\n const client = new TmimClient({\r\n baseUrl: config.baseUrl,\r\n ...(config.credential?.kind === 'apikey' && config.credential.token ? { token: config.credential.token } : {}),\r\n deviceId: config.deviceId,\r\n })\r\n\r\n // Optional credential: the plugin starts without one so it can be configured\r\n // later, but cannot authenticate until a token/credentials are supplied.\r\n const hasCredential =\r\n (config.credential?.kind === 'apikey' && !!config.credential.token) ||\r\n (config.credential?.kind === 'password' && !!config.credential.password)\r\n if (!hasCredential) {\r\n ctx.logger.warn(\r\n '[tmim] no credential configured — plugin started in idle mode. ' +\r\n 'Configure credential (Agent api_key or password) to enable messaging.',\r\n )\r\n } else if (config.credential!.kind === 'password') {\r\n await client.login(config.credential!.username, config.credential!.password)\r\n ctx.logger.info('[tmim] authenticated via password login')\r\n } else {\r\n client.setToken(config.credential!.token)\r\n ctx.logger.info('[tmim] using Agent api key')\r\n }\r\n\r\n // Optionally drive Agent Takeover configuration (only if credentials exist).\r\n if (config.enableAgentTakeover) {\r\n if (!hasCredential) {\r\n ctx.logger.warn('[tmim] enableAgentTakeover requires a credential; skipping takeover setup')\r\n } else {\r\n const setup = await client.configureAgentTakeover({\r\n ...(config.webhook.enabled\r\n ? { pushMode: 1 as const, endpointUrl: config.webhook.path }\r\n : { pushMode: 3 as const }), // WEBHOOK or PULL\r\n capabilities: ['text'],\r\n modelInfo: config.agent.model,\r\n })\r\n if (setup.webhookSecret && config.webhook.enabled) {\r\n ctx.logger.warn(\r\n '[tmim] NEW webhook_secret issued (shown only once) — save it and set webhook.secret. ' +\r\n 'Rotate anytime via POST /v1/me/agent-takeover/rotate-webhook-secret',\r\n )\r\n }\r\n }\r\n }\r\n\r\n const bridge = new TmimBridge(ctx, {\r\n getClient: () => client,\r\n agentOptions: { provider: config.agent.provider, model: config.agent.model },\r\n cwd: config.cwd,\r\n })\r\n\r\n // Register all resources in one effect so disposal unwinds them in order.\r\n ctx.effect(function* () {\r\n if (config.webhook.enabled) {\r\n yield mountTmimWebhook(ctx, {\r\n path: config.webhook.path,\r\n secret: config.webhook.secret,\r\n maxSkewSeconds: config.webhook.maxSkewSeconds,\r\n handle: (event: WebhookEvent) => handleWebhookEvent(bridge, event),\r\n })\r\n }\r\n\r\n if (config.tools) {\r\n yield registerTmimTools(ctx, () => client)\r\n }\r\n\r\n yield bridge.start()\r\n ctx.logger.info('[tmim] started (webhook=%s, tools=%s)', config.webhook.enabled, config.tools)\r\n }, 'tmim')\r\n}\r\n\r\nfunction handleWebhookEvent(bridge: TmimBridge, event: WebhookEvent): void {\r\n if (event.event !== 'message.created' && event.event !== 'mention.created') return\r\n const data = event.data as { sender?: { actor_type?: number } } & Record<string, any>\r\n // Skip messages the agent itself sent (actor_type === 2 is an Agent).\r\n if (data.sender?.actor_type === 2) return\r\n const replyTo = Number(data.reply_to ?? 0) || undefined\r\n void bridge.ingestMessage({\r\n convId: Number(data.conv_id),\r\n senderId: Number(data.sender_id),\r\n text: data.content?.text,\r\n ...(replyTo === undefined ? {} : { replyToMessageId: replyTo }),\r\n })\r\n}\r\n"],"mappings":"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"}
|
package/package.json
CHANGED