@roll-agent/smart-reply-agent 1.1.0 → 1.2.0

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/SKILL.md CHANGED
@@ -30,17 +30,51 @@ npm 包名:`@roll-agent/smart-reply-agent`
30
30
 
31
31
  ## Tools
32
32
 
33
+ 完整 inputSchema 可通过 `roll agent tools smart-reply-agent`(或 `--json`)查询。
34
+
33
35
  - `generate_reply(candidateMessage, conversationHistory?, candidateInfo?, preferredBrand?, channelType?, defaultWechatId?, industryVoiceId?, turnIndex?, modelConfig?, target)`
34
36
  调用 Reply Authority Service 的 `POST /generate-signed-reply`,返回 `suggestedReply`、`signedEnvelope`、`envelopeExp`、`confidence`、`stage` 和可选 `diagnostics`。`target` 为必填,至少包含 `platform=zhipin`、`conversationId`、`candidateId`,以及以下两种 recruiter 绑定方式之一:
35
37
  1. 直接传完整绑定:`tenantId + recruiterBinding`
36
38
  2. 便利代理模式:`recruiterUsername`(smart-reply 会先调用 `POST /resolve-recruiter-binding` 解析出 `tenantId + recruiterBinding`)
37
39
 
40
+ Minimal valid input 示例(代理模式):
41
+
42
+ ```json
43
+ {
44
+ "candidateMessage": "你好,我想了解一下这个岗位",
45
+ "target": {
46
+ "platform": "zhipin",
47
+ "conversationId": "642438677-0",
48
+ "candidateId": "642438677-0",
49
+ "recruiterUsername": "郭晓阳"
50
+ }
51
+ }
52
+ ```
53
+
54
+ Minimal valid input 示例(直接模式):
55
+
56
+ ```json
57
+ {
58
+ "candidateMessage": "你好,我想了解一下这个岗位",
59
+ "target": {
60
+ "platform": "zhipin",
61
+ "tenantId": "chengdu-liujie",
62
+ "conversationId": "642438677-0",
63
+ "candidateId": "642438677-0",
64
+ "recruiterBinding": { "platform": "zhipin", "username": "郭晓阳" }
65
+ }
66
+ }
67
+ ```
68
+
69
+ - `diagnostic_status()` — 返回本 agent 进程里声明过的 env key 的 `{present, fingerprint}`(SHA256 前 8 位,不泄漏 value)。`roll doctor` / `roll agent info` 据此对比 yaml 声明与运行态,检测 env drift(详见 roll-core skill template)。
70
+
38
71
  ## Reply Authority 集成说明
39
72
 
40
73
  - `generate_reply` 不再保留本地 pipeline fallback
41
74
  - 若缺少 `REPLY_AUTHORITY_URL` 或 `REPLY_AUTHORITY_BEARER_TOKEN`,tool 会直接报错
42
75
  - 实际回复生成、reply-policy、FactGate、ReplyGate、年龄校验都在云端执行
43
76
  - 返回的 `signedEnvelope` 为 v2 信封,已绑定 `tenantId + recruiterBinding + conversationId + candidateId`,供 `browser-use-agent.zhipin_send_reply` 本地验签后发送
77
+ - 调用失败时抛 `ReplyAuthorityRequestError`,携带 `meta: {url, timeoutMs, requestId}` 与 `Error.cause` 链。通过 `roll run` 运行时 stderr 会展开 `cause: ...` 行用于定位;透传的 `x-request-id` 可跨服务端追踪
44
78
 
45
79
  ## Environment Variables
46
80
 
@@ -0,0 +1,24 @@
1
+ import { z } from "zod";
2
+ export declare const SMART_REPLY_DECLARED_ENV_KEYS: readonly ["REPLY_AUTHORITY_URL", "REPLY_AUTHORITY_BEARER_TOKEN"];
3
+ export declare const EffectiveEnvSourceSchema: z.ZodObject<{
4
+ present: z.ZodBoolean;
5
+ fingerprint: z.ZodOptional<z.ZodString>;
6
+ }, "strip", z.ZodTypeAny, {
7
+ present: boolean;
8
+ fingerprint?: string | undefined;
9
+ }, {
10
+ present: boolean;
11
+ fingerprint?: string | undefined;
12
+ }>;
13
+ export declare const EffectiveEnvSourcesSchema: z.ZodRecord<z.ZodString, z.ZodObject<{
14
+ present: z.ZodBoolean;
15
+ fingerprint: z.ZodOptional<z.ZodString>;
16
+ }, "strip", z.ZodTypeAny, {
17
+ present: boolean;
18
+ fingerprint?: string | undefined;
19
+ }, {
20
+ present: boolean;
21
+ fingerprint?: string | undefined;
22
+ }>>;
23
+ export type EffectiveEnvSources = z.infer<typeof EffectiveEnvSourcesSchema>;
24
+ export declare function collectEffectiveEnvSources(names: readonly string[], env?: NodeJS.ProcessEnv): EffectiveEnvSources;
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- import{defineAgent as e}from"@roll-agent/sdk";import{defineTool as t}from"@roll-agent/sdk";import{z as r}from"zod";import{z as n}from"zod";var i=n.object({name:n.string(),baseURL:n.string(),description:n.string()}),o=n.record(n.string(),i),a=n.object({chatModel:n.string().optional(),classifyModel:n.string().optional(),replyModel:n.string().optional(),providerConfigs:o.optional()});import{z as s}from"zod";var c=["trust_building","private_channel","qualify_candidate","job_consultation","interview_scheduling","onboard_followup"],d=s.enum(c);import{z as g}from"zod";var l=g.object({name:g.string().optional(),position:g.string().optional(),expectedPosition:g.string().optional(),communicationPosition:g.string().optional(),age:g.string().optional(),gender:g.string().optional(),experience:g.string().optional(),education:g.string().optional(),expectedSalary:g.string().optional(),expectedLocation:g.string().optional(),jobAddress:g.string().optional(),height:g.string().optional(),weight:g.string().optional(),healthCertificate:g.boolean().optional(),activeTime:g.string().optional(),info:g.array(g.string()).optional(),fullText:g.string().optional()}),u=r.object({platform:r.literal("zhipin"),username:r.string().min(1),accountId:r.string().min(1).optional()}),p=r.object({platform:r.literal("zhipin"),tenantId:r.string().min(1).optional(),conversationId:r.string().min(1),candidateId:r.string().min(1)}),m=p.extend({recruiterBinding:u.optional(),recruiterUsername:r.string().min(1).optional()}).superRefine((e,t)=>{const n=void 0!==e.recruiterBinding,i=void 0!==e.recruiterUsername;n||i||t.addIssue({code:r.ZodIssueCode.custom,message:"target.recruiterBinding 或 target.recruiterUsername 至少需要提供一个。",path:["recruiterBinding"]}),n&&!i&&void 0===e.tenantId&&t.addIssue({code:r.ZodIssueCode.custom,message:"直接传 target.recruiterBinding 时,target.tenantId 也必须显式提供。",path:["tenantId"]}),n&&i&&void 0!==e.recruiterBinding&&e.recruiterBinding.username!==e.recruiterUsername&&t.addIssue({code:r.ZodIssueCode.custom,message:"target.recruiterUsername 必须与 target.recruiterBinding.username 一致。",path:["recruiterUsername"]})}),f=p.extend({tenantId:r.string().min(1),recruiterBinding:u}),y=r.object({candidateMessage:r.string().describe("候选人发送的消息"),conversationHistory:r.array(r.string()).optional().describe("对话历史(最近几轮)"),candidateInfo:l.optional().describe("候选人基本信息"),preferredBrand:r.string().optional().describe("偏好品牌"),channelType:r.enum(["public","private"]).optional().describe("渠道类型: public(BOSS直聘) 或 private(微信)"),defaultWechatId:r.string().optional().describe("默认微信号"),industryVoiceId:r.string().optional().describe("行业语调ID"),turnIndex:r.number().int().min(1).optional().describe("当前会话回复轮次"),modelConfig:a.optional().describe("模型配置覆盖"),target:m.describe("签名绑定目标:租户、会话和候选人标识")}),I=y.omit({target:!0}).extend({target:f,requestId:r.string().optional()}),b=r.object({suggestedReply:r.string(),signedEnvelope:r.string().describe("Reply Authority Service v2 紧凑签名信封"),envelopeExp:r.number().int(),confidence:r.number(),stage:d,replyPolicySource:r.enum(["file","default"]),latencyMs:r.number().optional(),shouldExchangeWechat:r.boolean().optional(),error:r.string().optional(),diagnostics:r.record(r.unknown()).optional()}),h=r.object({platform:r.literal("zhipin"),username:r.string().min(1),accountId:r.string().min(1).optional()}),v=r.object({tenantId:r.string().min(1),recruiterBinding:u}),w=r.object({statusCode:r.number().int(),error:r.string(),message:r.string()}),B=2e4;function R(e){const t=process.env[e]?.trim();if(!t)throw new Error(`${e} 未配置,smart-reply-agent 无法调用 Reply Authority Service。`);return t}function S(){return{baseUrl:R("REPLY_AUTHORITY_URL"),bearerToken:R("REPLY_AUTHORITY_BEARER_TOKEN")}}function A(e,t){const r=e.endsWith("/")?e:`${e}/`;return new URL(t,r).toString()}function E(e){return{"Content-Type":"application/json",Authorization:`Bearer ${e.bearerToken}`}}function U(e,t){const r=w.safeParse(t);return r.success?`Reply Authority Service 请求失败 (${e}): ${r.data.message}`:`Reply Authority Service 请求失败 (${e})`}async function x(e){const t=await e.text();if(0===t.trim().length)return null;try{return JSON.parse(t)}catch{throw new Error("Reply Authority Service 返回了非 JSON 响应。")}}async function T(e,t,r,n){const i=await fetch(A(e.baseUrl,t),{method:"POST",headers:E(e),body:JSON.stringify(r),signal:n}),o=await x(i);if(!i.ok)throw new Error(U(i.status,o));return o}function j(e){return h.parse({platform:e.platform,username:e.recruiterUsername})}async function _(e,t,r){const n=j(t),i=await T(e,"resolve-recruiter-binding",n,r);return v.parse(i)}function z(e,t){if(void 0!==t.tenantId&&t.tenantId!==e.tenantId)throw new Error(`Reply Authority Service recruiter 解析结果与 target.tenantId 不一致:${e.tenantId}`);return{tenantId:e.tenantId,recruiterBinding:e.recruiterBinding}}async function C(e,t,r){if(void 0!==e.target.recruiterBinding&&void 0!==e.target.tenantId)return I.parse({...e,target:{platform:e.target.platform,tenantId:e.target.tenantId,conversationId:e.target.conversationId,candidateId:e.target.candidateId,recruiterBinding:e.target.recruiterBinding}});const n=z(await _(t,e.target,r),e.target);return I.parse({...e,target:{platform:e.target.platform,tenantId:n.tenantId,conversationId:e.target.conversationId,candidateId:e.target.candidateId,recruiterBinding:n.recruiterBinding}})}async function $(e){const t=y.parse(e),r=I.safeParse(t),n=S(),i=new AbortController,o=setTimeout(()=>i.abort(),B);try{const e=r.success?r.data:await C(t,n,i.signal),o=await T(n,"generate-signed-reply",e,i.signal);return b.parse(o)}catch(e){if(e instanceof Error&&"AbortError"===e.name)throw new Error("Reply Authority Service 请求超时。");throw e}finally{clearTimeout(o)}}var P=t({name:"generate_reply",description:"根据候选人消息生成智能招聘回复,并向 Reply Authority Service 请求签名信封;调用方必须提供 target 以绑定会话和招聘者身份,可直接传 tenantId+recruiterBinding,或只传 recruiterUsername 交给 smart-reply 代理解析。",input:y,output:b,execute:async(e,t)=>{t.logger.info(`Processing message: ${e.candidateMessage.slice(0,50)}...`);const r=await $(e);return t.logger.info(`Signed reply generated. Stage: ${r.stage}, Confidence: ${r.confidence}`),r}}),O=e({name:"smart-reply-agent",tools:[P]});O.listen().catch(e=>{console.error("Fatal error:",e),process.exit(1)});
1
+ import{defineAgent as e}from"@roll-agent/sdk";import{defineTool as t}from"@roll-agent/sdk";import{z as r}from"zod";import{createHash as n}from"node:crypto";import{z as i}from"zod";var o=["REPLY_AUTHORITY_URL","REPLY_AUTHORITY_BEARER_TOKEN"],a=/^[0-9a-f]{8}$/,s=i.object({present:i.boolean(),fingerprint:i.string().regex(a).optional()}),c=i.record(s);function d(e,t=process.env){return Object.fromEntries(e.map(e=>{const r=t[e];return"string"==typeof r&&r.length>0?[e,{present:!0,fingerprint:u(r)}]:[e,{present:!1}]}))}function u(e){return n("sha256").update(e).digest("hex").slice(0,8)}var g=r.object({effectiveEnvSources:c}),l=t({name:"diagnostic_status",description:"返回 smart-reply 运行态进程中已声明环境变量的存在状态与短指纹",input:r.object({}),output:g,execute:async(e,t)=>(t.logger.info("Querying smart-reply diagnostic status"),{effectiveEnvSources:d(o)})});import{defineTool as p}from"@roll-agent/sdk";import{randomUUID as m}from"node:crypto";import{z as f}from"zod";import{z as y}from"zod";var I=y.object({name:y.string(),baseURL:y.string(),description:y.string()}),h=y.record(y.string(),I),b=y.object({chatModel:y.string().optional(),classifyModel:y.string().optional(),replyModel:y.string().optional(),providerConfigs:h.optional()});import{z as v}from"zod";var R=["trust_building","private_channel","qualify_candidate","job_consultation","interview_scheduling","onboard_followup"],w=v.enum(R);import{z as S}from"zod";var E=S.object({name:S.string().optional(),position:S.string().optional(),expectedPosition:S.string().optional(),communicationPosition:S.string().optional(),age:S.string().optional(),gender:S.string().optional(),experience:S.string().optional(),education:S.string().optional(),expectedSalary:S.string().optional(),expectedLocation:S.string().optional(),jobAddress:S.string().optional(),height:S.string().optional(),weight:S.string().optional(),healthCertificate:S.boolean().optional(),activeTime:S.string().optional(),info:S.array(S.string()).optional(),fullText:S.string().optional()}),B=f.object({platform:f.literal("zhipin"),username:f.string().min(1),accountId:f.string().min(1).optional()}),A=f.object({platform:f.literal("zhipin"),tenantId:f.string().min(1).optional(),conversationId:f.string().min(1),candidateId:f.string().min(1)}),T=A.extend({recruiterBinding:B.optional(),recruiterUsername:f.string().min(1).optional()}).superRefine((e,t)=>{const r=void 0!==e.recruiterBinding,n=void 0!==e.recruiterUsername;r||n||t.addIssue({code:f.ZodIssueCode.custom,message:"target.recruiterBinding 或 target.recruiterUsername 至少需要提供一个。",path:["recruiterBinding"]}),r&&!n&&void 0===e.tenantId&&t.addIssue({code:f.ZodIssueCode.custom,message:"直接传 target.recruiterBinding 时,target.tenantId 也必须显式提供。",path:["tenantId"]}),r&&n&&void 0!==e.recruiterBinding&&e.recruiterBinding.username!==e.recruiterUsername&&t.addIssue({code:f.ZodIssueCode.custom,message:"target.recruiterUsername 必须与 target.recruiterBinding.username 一致。",path:["recruiterUsername"]})}),x=A.extend({tenantId:f.string().min(1),recruiterBinding:B}),j=f.object({candidateMessage:f.string().describe("候选人发送的消息"),conversationHistory:f.array(f.string()).optional().describe("对话历史(最近几轮)"),candidateInfo:E.optional().describe("候选人基本信息"),preferredBrand:f.string().optional().describe("偏好品牌"),channelType:f.enum(["public","private"]).optional().describe("渠道类型: public(BOSS直聘) 或 private(微信)"),defaultWechatId:f.string().optional().describe("默认微信号"),industryVoiceId:f.string().optional().describe("行业语调ID"),turnIndex:f.number().int().min(1).optional().describe("当前会话回复轮次"),modelConfig:b.optional().describe("模型配置覆盖"),target:T.describe("签名绑定目标:租户、会话和候选人标识")}),q=j.omit({target:!0}).extend({target:x,requestId:f.string().optional()}),U=f.object({suggestedReply:f.string(),signedEnvelope:f.string().describe("Reply Authority Service v2 紧凑签名信封"),envelopeExp:f.number().int(),confidence:f.number(),stage:w,replyPolicySource:f.enum(["file","default"]),latencyMs:f.number().optional(),shouldExchangeWechat:f.boolean().optional(),error:f.string().optional(),diagnostics:f.record(f.unknown()).optional()}),_=f.object({platform:f.literal("zhipin"),username:f.string().min(1),accountId:f.string().min(1).optional()}),$=f.object({tenantId:f.string().min(1),recruiterBinding:B}),z=f.object({statusCode:f.number().int(),error:f.string(),message:f.string()}),O=2e4,P=class extends Error{meta;constructor(e,t){super(`${e} (${H(t.meta)})`,{cause:t.cause}),this.name="ReplyAuthorityRequestError",this.meta=t.meta}};function M(e){const t=process.env[e]?.trim();if(!t)throw new Error(`${e} 未配置,smart-reply-agent 无法调用 Reply Authority Service。`);return t}function C(){return{baseUrl:M("REPLY_AUTHORITY_URL"),bearerToken:M("REPLY_AUTHORITY_BEARER_TOKEN")}}function L(e,t){const r=e.endsWith("/")?e:`${e}/`;return new URL(t,r).toString()}function Y(e,t){return{"Content-Type":"application/json",Authorization:`Bearer ${e.bearerToken}`,"x-request-id":t.requestId}}function k(e,t){const r=z.safeParse(t);return r.success?`Reply Authority Service 请求失败 (${e}): ${r.data.message}`:`Reply Authority Service 请求失败 (${e})`}function H(e){const t=[`url=${e.url}`,`timeoutMs=${String(e.timeoutMs)}`];return void 0!==e.requestId&&t.push(`requestId=${e.requestId}`),t.join(", ")}function N(e,t,r){return{url:L(e.baseUrl,t),timeoutMs:r.timeoutMs,requestId:r.requestId}}function J(e,t){return e instanceof P?e:e instanceof Error&&"AbortError"===e.name?new P("Reply Authority Service 请求超时。",{cause:e,meta:t}):e instanceof Error?new P(e.message,{cause:e,meta:t}):new P("Reply Authority Service 请求失败。",{cause:e,meta:t})}function W(e,t,r,n){const i=e.safeParse(t);if(i.success)return i.data;throw new P(`${n} 响应校验失败。`,{cause:i.error,meta:r})}async function Z(e){const t=await e.text();if(0===t.trim().length)return null;try{return JSON.parse(t)}catch{throw new Error("Reply Authority Service 返回了非 JSON 响应。")}}async function K(e,t,r,n){const i=N(e,t,n);try{const t=await fetch(i.url,{method:"POST",headers:Y(e,n),body:JSON.stringify(r),signal:n.signal}),o=await Z(t);if(!t.ok)throw new Error(k(t.status,o));return o}catch(e){throw J(e,i)}}function D(e){return _.parse({platform:e.platform,username:e.recruiterUsername})}async function F(e,t,r){const n=D(t),i=await K(e,"resolve-recruiter-binding",n,r);return W($,i,N(e,"resolve-recruiter-binding",r),"Reply Authority Service recruiter 解析")}function Q(e,t,r){if(void 0!==t.tenantId&&t.tenantId!==e.tenantId)throw new P(`Reply Authority Service recruiter 解析结果与 target.tenantId 不一致:${e.tenantId}`,{meta:r});return{tenantId:e.tenantId,recruiterBinding:e.recruiterBinding}}async function V(e,t,r){if(void 0!==e.target.recruiterBinding&&void 0!==e.target.tenantId)return q.parse({...e,target:{platform:e.target.platform,tenantId:e.target.tenantId,conversationId:e.target.conversationId,candidateId:e.target.candidateId,recruiterBinding:e.target.recruiterBinding},requestId:r.requestId});const n=Q(await F(t,e.target,r),e.target,N(t,"resolve-recruiter-binding",r));return q.parse({...e,target:{platform:e.target.platform,tenantId:n.tenantId,conversationId:e.target.conversationId,candidateId:e.target.candidateId,recruiterBinding:n.recruiterBinding},requestId:r.requestId})}async function G(e){const t=j.parse(e),r=q.safeParse(t),n=C(),i=new AbortController,o={signal:i.signal,timeoutMs:O,requestId:m()},a=setTimeout(()=>i.abort(),O);try{const e=r.success?q.parse({...r.data,requestId:r.data.requestId??o.requestId}):await V(t,n,o),i=await K(n,"generate-signed-reply",e,o);return W(U,i,N(n,"generate-signed-reply",o),"Reply Authority Service 签名回复")}finally{clearTimeout(a)}}var X=p({name:"generate_reply",description:"根据候选人消息生成智能招聘回复,并向 Reply Authority Service 请求签名信封;调用方必须提供 target 以绑定会话和招聘者身份,可直接传 tenantId+recruiterBinding,或只传 recruiterUsername 交给 smart-reply 代理解析。",input:j,output:U,execute:async(e,t)=>{t.logger.info(`Processing message: ${e.candidateMessage.slice(0,50)}...`);const r=await G(e);return t.logger.info(`Signed reply generated. Stage: ${r.stage}, Confidence: ${r.confidence}`),r}}),ee=e({name:"smart-reply-agent",tools:[X,l]});ee.listen().catch(e=>{console.error("Fatal error:",e),process.exit(1)});
@@ -1,2 +1,15 @@
1
1
  import { type GenerateReplyToolInput, type GenerateSignedReplyResponse } from "../types/reply-authority.ts";
2
+ interface ReplyAuthorityRequestMeta {
3
+ readonly url: string;
4
+ readonly timeoutMs: number;
5
+ readonly requestId?: string;
6
+ }
7
+ interface ReplyAuthorityRequestErrorOptions extends ErrorOptions {
8
+ readonly meta: ReplyAuthorityRequestMeta;
9
+ }
10
+ export declare class ReplyAuthorityRequestError extends Error {
11
+ readonly meta: ReplyAuthorityRequestMeta;
12
+ constructor(message: string, options: ReplyAuthorityRequestErrorOptions);
13
+ }
2
14
  export declare function generateSignedReply(input: GenerateReplyToolInput): Promise<GenerateSignedReplyResponse>;
15
+ export {};
@@ -0,0 +1,6 @@
1
+ export declare const diagnosticStatus: import("@roll-agent/sdk").ToolDefinition<{}, {
2
+ effectiveEnvSources: Record<string, {
3
+ present: boolean;
4
+ fingerprint?: string | undefined;
5
+ }>;
6
+ }>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@roll-agent/smart-reply-agent",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",