@lark-apaas/openclaw-extension-miaoda-coding 1.0.2 → 1.0.3
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.mjs +5 -5
- package/package.json +1 -1
- package/skills/miaoda-coding/SKILL.md +50 -20
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import{createRequire as e}from"node:module";import{spawn as t}from"node:child_process";import n from"node:fs";import r,{join as i,resolve as a}from"node:path";import{appendFile as o,mkdir as s,readFile as c,writeFile as
|
|
1
|
+
import{createRequire as e}from"node:module";import{spawn as t}from"node:child_process";import n from"node:fs";import r,{join as i,resolve as a}from"node:path";import{appendFile as o,mkdir as s,readFile as c,readdir as l,rm as u,writeFile as d}from"node:fs/promises";var f=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports),p=e(import.meta.url);function m(){return a(process.env.OPENCLAW_STATE_DIR||process.cwd())}function h(e){return i(e,`workspace`)}function g(e,t){return i(e,`workspace`,t)}function _(e){return i(e,`.agent`)}var v=f(((e,t)=>{var n=Object.defineProperty,r=Object.getOwnPropertyDescriptor,i=Object.getOwnPropertyNames,a=Object.prototype.hasOwnProperty,o=(e,t)=>{for(var r in t)n(e,r,{get:t[r],enumerable:!0})},s=(e,t,o,s)=>{if(t&&typeof t==`object`||typeof t==`function`)for(let c of i(t))!a.call(e,c)&&c!==o&&n(e,c,{get:()=>t[c],enumerable:!(s=r(t,c))||s.enumerable});return e},c=e=>s(n({},`__esModule`,{value:!0}),e),l={};o(l,{DEFAULT_CLOCK_TOLERANCE_SEC:()=>f,DEFAULT_JWT_EXPIRE_TIME_MS:()=>d,HttpClient:()=>N,HttpError:()=>A,generateJWTToken:()=>h,parseJWTTokenWithVerify:()=>g,registerPlatformPlugin:()=>T,resolvePlatformBaseURL:()=>w}),t.exports=c(l);var u=p(`crypto`),d=1800*1e3,f=60,m={alg:`HS256`,typ:`JWT`};function h(e,t){let n=Math.floor(Date.now()/1e3),r={...e,iss:e.access_key,iat:n,nbf:n,exp:n+Math.floor(t.expireTimeMs/1e3),jti:(0,u.randomUUID)()},i=v(JSON.stringify(m)),a=v(JSON.stringify(r));return`${i}.${a}.${_(`${i}.${a}`,t.secretKey)}`}function g(e,t,n){let r=e.split(`.`);if(r.length!==3)throw Error(`invalid JWT token format`);let[i,a,o]=r;if(JSON.parse(y(i)).alg!==`HS256`)throw Error(`unsupported JWT alg`);let s=_(`${i}.${a}`,t.secretKey),c=Buffer.from(s),l=Buffer.from(o);if(c.length!==l.length||!(0,u.timingSafeEqual)(c,l))throw Error(`JWT signature verification failed`);let d=JSON.parse(y(a));if(!n?.skipExpiration){let e=n?.clockTolerance??f,t=Math.floor(Date.now()/1e3);if(d.exp!==void 0&&d.exp+e<t)throw Error(`JWT token expired at ${new Date(d.exp*1e3).toISOString()}`);if(d.nbf!==void 0&&d.nbf-e>t)throw Error(`JWT token not yet valid, will be valid at ${new Date(d.nbf*1e3).toISOString()}`);if(d.iat!==void 0&&d.iat-e>t)throw Error(`JWT token issued in the future at ${new Date(d.iat*1e3).toISOString()}`)}return d}function _(e,t){return(0,u.createHmac)(`sha256`,t).update(e).digest(`base64`).replace(/=/g,``).replace(/\+/g,`-`).replace(/\//g,`_`)}function v(e){return Buffer.from(e).toString(`base64`).replace(/=/g,``).replace(/\+/g,`-`).replace(/\//g,`_`)}function y(e){let t=(4-(e.length%4||4))%4,n=`${e}${`=`.repeat(t)}`.replace(/-/g,`+`).replace(/_/g,`/`);return Buffer.from(n,`base64`).toString(`utf8`)}var b=class{cache=new Map;config;constructor(e){this.config={refreshBeforeMs:300*1e3,...e}}getToken(e){let t=this.getCacheKey(e),n=this.cache.get(t),r=Date.now();if(n&&n.expiresAtMs-r>this.config.refreshBeforeMs)return n.token;let i=h(e,this.config),a=r+this.config.expireTimeMs;return this.cache.set(t,{token:i,expiresAtMs:a}),i}clearCache(){this.cache.clear()}clearCacheFor(e){let t=this.getCacheKey(e);this.cache.delete(t)}getCacheKey(e){return[e.access_key,e.tenant_id?.toString()||``,e.user_id||``,e.app_id||``,e.app_env||``,e.sandbox_id||``].join(`:`)}getCacheStats(){let e=Date.now(),t=0,n=0;for(let r of this.cache.values())r.expiresAtMs>e?t++:n++;return{total:this.cache.size,valid:t,expired:n}}},x=`FORCE_AUTHN_INNERAPI_DOMAIN`,S=`FORCE_AUTHN_ACCESS_KEY`,C=`FORCE_AUTHN_ACCESS_SECRET`;function w(e){if(!e?.enabled)return e?.baseURL;if(e.baseURL)return e.baseURL;let t=e.domainEnv||x,n=process.env[t];if(!n)throw Error(`\u5E73\u53F0\u6A21\u5F0F\u9700\u8981\u57FA\u7840\u57DF\u540D\uFF0C\u8BF7\u8BBE\u7F6E\u73AF\u5883\u53D8\u91CF ${t}`);return n}function T(e,t){if(!t.enabled)return;E(`platform.defaultClaims`,t.defaultClaims);let n=t.accessKeyEnv||S,r=t.secretKeyEnv||C,i=t.accessKey??(process.env[n]||``),a=new b({accessKey:i,secretKey:t.secretKey??(process.env[r]||``),expireTimeMs:t.expireTimeMs??d,refreshBeforeMs:t.refreshBeforeMs});return e.request.use(e=>{E(`request.platformAuth.customClaims`,e.platformAuth?.customClaims);let n={...t.defaultClaims||{},...e.platformAuth?.customClaims||{},access_key:i},r=a.getToken(n),o={...e.headers,Authorization:`Bearer ${r}`,"x-api-key":i};return{...e,headers:o}}),a}function E(e,t){if(t&&Object.prototype.hasOwnProperty.call(t,`access_key`))throw Error(`${e} \u4E0D\u5141\u8BB8\u8BBE\u7F6E access_key`)}var D=class{interceptors=[];use(e,t){return this.interceptors.push({onFulfilled:e,onRejected:t}),this.interceptors.length-1}eject(e){this.interceptors[e]&&(this.interceptors[e]=null)}clear(){this.interceptors=[]}forEach(e){this.interceptors.forEach(t=>{t!==null&&e(t)})}};function O(e){if(typeof e!=`object`||!e)return!1;let t=Object.getPrototypeOf(e);return t===Object.prototype||t===null}function k(e){if(!e)return{};if(e instanceof Headers){let t={};return e.forEach((e,n)=>{t[n]=e}),t}if(Array.isArray(e))return e.reduce((e,[t,n])=>(e[t]=n,e),{});let t={};return Object.entries(e).forEach(([e,n])=>{t[e]=Array.isArray(n)?n.join(`,`):n}),t}var A=class e extends Error{isHttpError=!0;response;config;constructor(t,n,r){super(r||`Request failed with status ${t?.status||`unknown`}`),this.name=`HttpError`,this.response=t,this.config=j(n),Object.setPrototypeOf(this,e.prototype)}};function j(e){let{headers:t,...n}=e;return{...n,headers:M(t)}}function M(e){if(!e)return;let t=k(e),n={},r=[`authorization`,`x-api-key`,`cookie`,`x-secret`];for(let[e,i]of Object.entries(t)){let t=e.toLowerCase();r.includes(t)?n[e]=`[REDACTED]`:n[e]=i}return n}var N=class{defaultConfig;securityConfig;interceptors={request:new D,response:new D};constructor(e){let{platform:t,security:n,...r}=e||{};this.defaultConfig={timeout:5e3,...r};let i=n?.strictMode??!1;this.securityConfig={allowedProtocols:i?[`http:`,`https:`]:null,maxResponseSize:i?50*1024*1024:0,strictMode:i,...n},t?.enabled&&(this.defaultConfig.baseURL||(this.defaultConfig.baseURL=w(t)),T(this.interceptors,t))}async get(e,t){return this.request({...t,url:e,method:`GET`})}async post(e,t,n){let r={...n?.headers},i=t;return O(t)&&(i=JSON.stringify(t),Object.keys(r).some(e=>e.toLowerCase()===`content-type`)||(r[`Content-Type`]=`application/json`)),this.request({...n,url:e,method:`POST`,body:i,headers:r})}async put(e,t,n){let r={...n?.headers},i=t;return O(t)&&(i=JSON.stringify(t),Object.keys(r).some(e=>e.toLowerCase()===`content-type`)||(r[`Content-Type`]=`application/json`)),this.request({...n,url:e,method:`PUT`,body:i,headers:r})}async delete(e,t){return this.request({...t,url:e,method:`DELETE`})}async patch(e,t,n){let r={...n?.headers},i=t;return O(t)&&(i=JSON.stringify(t),Object.keys(r).some(e=>e.toLowerCase()===`content-type`)||(r[`Content-Type`]=`application/json`)),this.request({...n,url:e,method:`PATCH`,body:i,headers:r})}async request(e){let t=k(this.defaultConfig.headers),n=k(e.headers),r={};for(let e in t)r[e.toLowerCase()]={key:e,value:t[e]};for(let e in n)r[e.toLowerCase()]={key:e,value:n[e]};let i={};for(let e in r){let{key:t,value:n}=r[e];i[t]=n}let a={...this.defaultConfig,...e,headers:i};try{a=await this.runRequestInterceptors(a)}catch(e){return Promise.reject(e)}a.headers=k(a.headers);let o=this.buildUrl(a.url,a.params),s=a.timeout||this.defaultConfig.timeout||5e3,c=new AbortController,l=setTimeout(()=>c.abort(),s),u=a.signal;u&&(u.aborted?(clearTimeout(l),c.abort()):u.addEventListener(`abort`,()=>c.abort(),{once:!0}));let{platformAuth:d,params:f,timeout:p,baseURL:m,url:h,...g}=a;try{let e=await fetch(o,{...g,signal:c.signal});if(clearTimeout(l),this.securityConfig.maxResponseSize>0){let t=e.headers.get(`content-length`);if(t){let e=parseInt(t,10);if(e>this.securityConfig.maxResponseSize)throw Error(`Response size ${e} bytes exceeds limit of ${this.securityConfig.maxResponseSize} bytes`)}}if(!e.ok){let t=new A(e,a);return this.runResponseInterceptors(Promise.reject(t))}return this.runResponseInterceptors(Promise.resolve(e))}catch(e){clearTimeout(l);let t=e instanceof A?e:new A(void 0,a,e.name===`AbortError`?`Request aborted`:e.message);return this.runResponseInterceptors(Promise.reject(t))}}runRequestInterceptors(e){let t=Promise.resolve(e),n=[];this.interceptors.request.forEach(e=>{n.push(e)});for(let e of n)t=t.then(e.onFulfilled,e.onRejected);return t}async runResponseInterceptors(e){let t=e,n=[];this.interceptors.response.forEach(e=>{n.push(e)});for(let e of n)t=t.then(e.onFulfilled,e.onRejected);return t}buildUrl(e,t){let n=this.defaultConfig.baseURL,r;r=!n||/^https?:\/\//i.test(e)?e:n.replace(/\/+$/,``)+`/`+e.replace(/^\/+/,``);let i=new URL(r),{allowedProtocols:a}=this.securityConfig;if(a&&!a.includes(i.protocol))throw Error(`Protocol ${i.protocol} is not allowed. Allowed: ${a.join(`, `)}`);if(t)for(let[e,n]of Object.entries(t))n!=null&&i.searchParams.set(e,String(n));return i.href}}}))();function y(){let e=process.env.app_id,t=process.env.FORCE_AUTHN_INNERAPI_DOMAIN,n=process.env.FORCE_AUTHN_ACCESS_SECRET,r=process.env.FORCE_AUTHN_ACCESS_KEY;if(!e||!t||!n||!r)throw Error(`studio-client: 缺少环境变量,需要: app_id, FORCE_AUTHN_INNERAPI_DOMAIN, FORCE_AUTHN_ACCESS_KEY, FORCE_AUTHN_ACCESS_SECRET`);return{appId:e,apiUrl:t,accessSecret:n,accessKey:r}}function b(...e){for(let t of e)if(typeof t==`string`&&t)return t}async function*x(e){let t=new TextDecoder,n=``;for await(let r of e){n+=t.decode(r,{stream:!0});let e=n.split(`
|
|
2
2
|
|
|
3
3
|
`);n=e.pop();for(let t of e){let e=t.split(`
|
|
4
|
-
`).find(e=>e.startsWith(`data:`));if(!e)continue;let n=e.slice(5).trim();if(!(!n||n===`[DONE]`))try{yield JSON.parse(n)}catch{}}}}var
|
|
5
|
-
`).map(e=>e.trim()).filter(Boolean)}catch{return[]}}function
|
|
6
|
-
`).filter(Boolean),s=null;try{s=JSON.parse(n.readFileSync(r.join(i,e,`.spark`,`meta.json`),`utf8`))}catch{}let c=!!s?.finalText,l=o.slice(-20).map(
|
|
7
|
-
`);return{content:[{type:`text`,text:JSON.stringify({status:`ok`,project_id:e,lines:o.length,progress:l,delivery_ready:c,finalTextAvailable:c,note:`miaoda_check_progress 只用于查看进度,不能根据 appId 或进度文本推导预览链接;最终链接只以 subagent completion result 中的 output/finalText 为准。`})}]}}catch{return{content:[{type:`text`,text:JSON.stringify({status:`no_progress`,project_id:t.project_id,message:`暂无进度记录`,delivery_ready:!1,finalTextAvailable:!1,note:`miaoda_check_progress 只用于查看进度,不能根据 appId 或进度文本推导预览链接;最终链接只以 subagent completion result 中的 output/finalText 为准。`})}]}}}});let o=900*1e3;e.registerTool({name:`miaoda_coding`,label:`妙搭`,description
|
|
4
|
+
`).find(e=>e.startsWith(`data:`));if(!e)continue;let n=e.slice(5).trim();if(!(!n||n===`[DONE]`))try{yield JSON.parse(n)}catch{}}}}var S=class{#e;#t;constructor(){this.#t=y(),this.#e=new v.HttpClient({baseURL:this.#t.apiUrl,timeout:9e5,platform:{enabled:!0,baseURL:this.#t.apiUrl,accessKey:this.#t.accessKey,secretKey:this.#t.accessSecret,defaultClaims:{appId:this.#t.appId}}})}async createSubApp(e={}){let t=`${this.#t.apiUrl}/api/v1/studio/innerapi/openclaw/sub_app/create`,n={};e.message&&(n.message=e.message);let r;try{r=await this.#e.post(t,n,{headers:{"Content-Type":`application/json`}})}catch(e){if(e instanceof v.HttpError&&e.response){let t=e.response.headers.get(`x-tt-logid`)??``;throw Error(`createSubApp 请求失败 (logID: ${t}): ${e.response.status}`)}throw e}let i=r.headers?.get?.(`x-tt-logid`)??``,a=await r.json();if(a.BaseResp?.StatusCode!==0&&a.BaseResp?.StatusMessage)throw Error(`createSubApp 业务错误 (logID: ${i}): ${a.BaseResp.StatusMessage}`);if(a.error_msg&&a.status_code!==`0`)throw Error(`createSubApp 服务错误 (logID: ${i}): ${a.error_msg}`);let o=b(a.appID,a.appId,a.app_id,a.data?.appID,a.data?.appId,a.data?.app_id),s=b(a.conversationID,a.conversationId,a.conversation_id,a.data?.conversationID,a.data?.conversationId,a.data?.conversation_id);if(!o){let e=Object.keys(a).join(`,`)||`(none)`,t=a.data&&typeof a.data==`object`&&Object.keys(a.data).join(`,`)||`(none)`;throw Error(`createSubApp 返回异常 (logID: ${i}): 缺少 appID,topLevelKeys=${e} dataKeys=${t}`)}return{appID:o,conversationID:s,logId:i}}async*streamChat(e){let t=`${this.#t.apiUrl}/api/v1/studio/innerapi/openclaw/sub_app/${e.appID}/stream_chat`,n={appID:e.appID,message:e.message};e.workDir&&(n.workDir=e.workDir);let r;try{r=await this.#e.post(t,n,{headers:{"Content-Type":`application/json`,Accept:`text/event-stream`}})}catch(e){if(e instanceof v.HttpError&&e.response){let t=e.response.headers.get(`x-tt-logid`)??``,n=Error(`streamChat 请求失败 (logID: ${t}): ${e.response.status}`);throw n.logId=t,n}let t=e.cause?` cause: ${e.cause.message||e.cause}`:``,n=e.code?` code: ${e.code}`:``;throw Error(`streamChat 网络错误: ${e.message}${n}${t}`)}yield{__meta:!0,logId:r.headers?.get?.(`x-tt-logid`)??``},yield*x(r.body)}};const C=`.spark`,w=`meta.json`;function T(e){return i(e,C,w)}async function E(e){try{return JSON.parse(await c(T(e),`utf8`))}catch{return null}}async function D(e){for(let t of[i(e,`..`,`..`,C,w),i(e,`..`,C,w)])try{return JSON.parse(await c(t,`utf8`))}catch{}return null}async function O(e,t){await s(i(e,C),{recursive:!0});let n=await E(e),r={...n??{},...t};if(!n){let t=await D(e);if(t)for(let[e,n]of Object.entries(t))e in r||(r[e]=n)}await d(T(e),JSON.stringify(r,null,2),`utf8`)}const k=`.spark`;function A(e){return i(e,k,`progress.txt`)}async function j(e){await s(i(e,k),{recursive:!0}),await d(A(e),``,`utf8`)}async function M(e,t){await o(A(e),`[${new Date().toISOString()}] ${t}\n`,`utf8`)}function N(e){return e?.data?.message??e?.Data?.Message??null}async function*P(e){let{cwd:t,task:n,signal:r}=e;try{let e=new S;await j(t),await M(t,`已接收需求`),yield{type:`progress`,message:`已接收需求`};let i=await E(t),a=!i?.appId,o=new Date().toISOString();if(a){let{appID:r,conversationID:a,logId:s}=await e.createSubApp({message:n});i={appId:r,conversationId:a,createdAt:o,updatedAt:o},await O(t,i),await M(t,`应用已创建(appId: ${r})`),yield{type:`app_created`,appId:r,conversationId:a,logId:s}}await M(t,`正在处理`),yield{type:`progress`,message:`正在处理`};let s=``,c=!1,l;for await(let a of e.streamChat({appID:i.appId,message:n,workDir:t})){if(r?.aborted){await M(t,`失败: cancelled`),yield{type:`failed`,error:`cancelled`};return}let e=a;if(e?.__meta){s=e.logId||``;continue}let n=N(e),i=n?.status,o=n?.type,u=n?.role,d=typeof n?.content==`string`?n.content:``,f=n?.delta?.type;!c&&u===`assistant`&&(i===`in_progress`||f===`text`)&&(c=!0,await M(t,`正在输出结果`),yield{type:`progress`,message:`正在输出结果`,logId:s||void 0}),!(u!==`assistant`||o!==`message`||i!==`completed`||!d)&&(l=d)}if(r?.aborted){await M(t,`失败: cancelled`),yield{type:`failed`,error:`cancelled`};return}let u=new Date().toISOString();await O(t,{...i,updatedAt:u,finalText:l}),await M(t,`处理完成`),yield{type:`completed`,appId:i.appId,logId:s||void 0,finalText:l}}catch(e){let n=e instanceof Error?e.message:String(e);try{await j(t),await M(t,`已接收需求`),await M(t,`失败: ${n}`)}catch{}yield{type:`failed`,error:n,logId:e?.logId}}}const F=[`research`,`design`,`feedback`];function I(e){return e.replace(/[^a-zA-Z0-9\u4e00-\u9fff-]/g,`-`).replace(/-+/g,`-`).replace(/^-|-$/g,``).slice(0,60)||`reference`}async function L(e){let t;try{t=await l(e)}catch{return 1}let n=0;for(let e of t){let t=e.match(/^(\d{3})-/);if(t){let e=parseInt(t[1],10);e>n&&(n=e)}}return n+1}async function R(e){try{let t=await c(i(e,`reference`,`manifest.json`),`utf8`);return JSON.parse(t)}catch{return{updatedAt:new Date().toISOString(),files:[]}}}async function z(e,t){await s(i(e,`reference`),{recursive:!0}),await d(i(e,`reference`,`manifest.json`),JSON.stringify(t,null,2),`utf8`)}async function B(e){let{appCwd:t,category:n,content:r,filename:a,mode:o=`append`}=e,c=i(t,`reference`,n);if(o===`replace`)try{await u(c,{recursive:!0,force:!0})}catch{}await s(c,{recursive:!0});let l=await L(c),f=`${n}/${`${String(l).padStart(3,`0`)}-${I(a??`reference`)}.md`}`,p=`reference/${f}`;await d(i(t,p),r,`utf8`);let m=await R(t);return o===`replace`&&(m.files=m.files.filter(e=>e.category!==n)),m.files.push({path:f,category:n,writtenAt:new Date().toISOString()}),m.updatedAt=new Date().toISOString(),await z(t,m),{relativePath:p,category:n,seq:l}}async function V(e){let t=i(e,`reference`),n;try{n=await l(t)}catch{return{totalFiles:0,categories:{}}}let r={},a=0;for(let e of n){if(!F.includes(e))continue;let n;try{n=(await l(i(t,e))).filter(e=>e.endsWith(`.md`)).sort()}catch{continue}n.length>0&&(r[e]={count:n.length,files:n},a+=n.length)}return{totalFiles:a,categories:r}}async function H(e){return(await V(e)).totalFiles>0}const U=/^[a-z0-9][a-z0-9-]{0,63}$/,W=new Set([`.`,`..`,`con`,`nul`,`prn`,`aux`]);function G(e){let t=e?.config?.plugins?.entries?.[`openclaw-extension-miaoda-coding`]?.config??{};return{verbose:t?.verbose===!0,hooks:{allowPromptInjection:t?.hooks?.allowPromptInjection!==!1}}}function K(e,n){if(!e)return;let i=r.dirname(process.execPath),a=t(`openclaw`,[`message`,`send`,`--channel`,`feishu`,`--target`,e,`--message`,n],{stdio:[`ignore`,`ignore`,`pipe`],env:{...process.env,PATH:`${i}:${process.env.PATH||``}`}});a.stderr.on(`data`,()=>{}),a.on(`close`,()=>{}),a.unref()}function q(e){return String(e??``).replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F]/g,``).replace(/\s+/g,` `).trim()}function J(e){let t=String(e??``).replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F]/g,``).trim();if(!t)throw Error(`task 不能为空`);if(t.length>4e3)throw Error(`task 不能超过 4000 个字符`);return t}function Y(e){let t=q(e);if(!X(t))throw Error(`project_id 不合法: "${t}"`);return t}function X(e){return U.test(e)&&!W.has(e)}function Z(e){if(e==null||e===``)return;let t=q(e);if(!/^(user|chat|thread):\S+$/.test(t))throw Error(`target 不合法`);return t}function ee(e){try{return n.readFileSync(e,`utf8`).split(`
|
|
5
|
+
`).map(e=>e.trim()).filter(Boolean)}catch{return[]}}function te(e){return e.replace(/^\[[^\]]+\]\s*/,``)}function Q(e){return e.replace(/应用已创建(appId:\s*[^)]+)/g,`应用已创建`)}function $(e){return e.slice(-3).map(te).map(Q).join(` | `)||`暂无进度记录`}function ne(e){let t=$(e);return t.includes(`失败:`)?`failed`:t.includes(`处理完成`)?`completed`:t===`暂无进度记录`?`pending`:`running`}var re={id:`openclaw-extension-miaoda-coding`,name:`妙搭`,description:`妙搭制品创建与修改`,register(e){let t=m(),i=h(t),a={verbose:!1,hooks:{allowPromptInjection:!0}},o=new Set([`miaoda_coding`]),s=new Set([`miaoda_write_reference`,`miaoda_check_progress`]);e.on(`before_tool_call`,(e,t)=>{let n=e.toolName,r=(t?.sessionKey??``).toLowerCase().split(`:`).includes(`subagent`);if(o.has(n)&&!r)return{block:!0,blockReason:`${n} 只能由 subagent 调用。请通过 sessions_spawn(runtime="subagent", mode="run") 派活,不要直接调用此工具。读 miaoda-coding skill 了解完整流程。`};if(s.has(n)&&r)return{block:!0,blockReason:`${n} 不能由 subagent 调用。subagent 不应直接操作参考资料或查询进度。`}}),e.on(`before_prompt_build`,(e,t)=>{if(G(t).hooks.allowPromptInjection)return{prependSystemContext:[`【硬规则 — 妙搭】`,`当用户要求创建或修改应用、网页、页面时,必须通过 sessions_spawn(runtime="subagent", mode="run") 交给妙搭。`,`读 miaoda-coding skill 后严格按指示操作。只传 runtime、mode、task 三个参数,不要传 streamTo/sandbox 等。`,`绝对不要自己写代码或用 exec 生成文件。如果 spawn 失败,重试而不是 fallback。`,`如果对话中有需要传递给 code agent 的上下文(研究结果、用户偏好等),先调 miaoda_write_reference 写入 reference 目录,再 spawn。用户反悔或需要替换之前的上下文时,使用 miaoda_write_reference 的 mode=replace。妙搭只做制品生成,不做研究。`,`不要用 sessions_history 读取子 agent 的 session,查进度用 miaoda_check_progress tool。`,`subagent 完成后只发一条消息(总结+预览链接),严禁发多条。不要调 message tool。预览链接必须使用加粗 Markdown 链接格式 **[url](url)**,不要发裸 URL。`].join(` `)}}),e.registerTool({name:`list_projects`,label:`列出妙搭项目`,description:`列出已创建的妙搭项目,返回 project_id、appId、updatedAt、finalText、status 和进度摘要。修改已有项目时必须先调此工具。`,parameters:{type:`object`,properties:{}},async execute(){let e=[];try{e=n.readdirSync(i,{withFileTypes:!0}).filter(e=>e.isDirectory()&&X(e.name)).map(e=>{let t=r.join(i,e.name,`.spark`,`meta.json`),a=r.join(i,e.name,`.spark`,`progress.txt`),o=null;try{o=JSON.parse(n.readFileSync(t,`utf8`))}catch{}let s=ee(a);return{project_id:e.name,appId:o?.appId??null,updatedAt:o?.updatedAt??null,finalText:o?.finalText??null,status:ne(s),progressSummary:$(s)}})}catch{}return{content:[{type:`text`,text:JSON.stringify({projects:e},null,2)}]}}}),e.registerTool({name:`miaoda_check_progress`,label:`查看妙搭项目进度`,description:`查看妙搭项目的生成进度。用户问"做到哪了"、"进度如何"、"好了吗"时调此工具。`,parameters:{type:`object`,properties:{project_id:{type:`string`,description:`项目目录名(从 list_projects 获取)`}},required:[`project_id`]},async execute(e,t){try{let e=Y(t.project_id),a=r.join(i,e,`.spark`,`progress.txt`),o=n.readFileSync(a,`utf8`).trim().split(`
|
|
6
|
+
`).filter(Boolean),s=null;try{s=JSON.parse(n.readFileSync(r.join(i,e,`.spark`,`meta.json`),`utf8`))}catch{}let c=!!s?.finalText,l=o.slice(-20).map(Q).join(`
|
|
7
|
+
`);return{content:[{type:`text`,text:JSON.stringify({status:`ok`,project_id:e,lines:o.length,progress:l,delivery_ready:c,finalTextAvailable:c,note:`miaoda_check_progress 只用于查看进度,不能根据 appId 或进度文本推导预览链接;最终链接只以 subagent completion result 中的 output/finalText 为准。`})}]}}catch{return{content:[{type:`text`,text:JSON.stringify({status:`no_progress`,project_id:t.project_id,message:`暂无进度记录`,delivery_ready:!1,finalTextAvailable:!1,note:`miaoda_check_progress 只用于查看进度,不能根据 appId 或进度文本推导预览链接;最终链接只以 subagent completion result 中的 output/finalText 为准。`})}]}}}}),e.registerTool({name:`miaoda_write_reference`,label:`写入妙搭参考资料`,description:`将参考资料写入妙搭项目的 reference 目录,供 code agent 在生成时自行查阅。按 category 分类写入。mode=append(默认)追加,mode=replace 替换该 category 全部内容(用于反悔/调整场景)。`,parameters:{type:`object`,properties:{project_id:{type:`string`,description:`项目目录名`},category:{type:`string`,enum:[`research`,`design`,`feedback`],description:`资料类别:research(调研结果)、design(设计要求)、feedback(用户反馈)`},content:{type:`string`,description:`参考资料内容(Markdown 格式,摘要+关键原文引用)`},filename:{type:`string`,description:`可选文件名(不含扩展名)`},mode:{type:`string`,enum:[`append`,`replace`],description:`写入模式:append(追加,默认)、replace(替换该 category 全部内容)`}},required:[`project_id`,`category`,`content`]},async execute(e,n){try{let e=Y(n.project_id);if(!F.includes(n.category))throw Error(`category 不合法,必须是 ${F.join(`、`)} 之一`);let r=n.content?.trim();if(!r)throw Error(`content 不能为空`);if(r.length>5e4)throw Error(`content 不能超过 50000 个字符`);let i=n.mode===`replace`?`replace`:`append`,a=g(t,e),o=await B({appCwd:a,category:n.category,content:r,filename:n.filename,mode:i}),s=await V(a);return{content:[{type:`text`,text:JSON.stringify({status:`ok`,...o,summary:s},null,2)}]}}catch(e){return{content:[{type:`text`,text:JSON.stringify({status:`error`,error:e instanceof Error?e.message:String(e)})}]}}}});let c=900*1e3;e.registerTool({name:`miaoda_coding`,label:`妙搭`,description:`创建或修改妙搭应用/网页。只接受生成指令,不接受搜索/调研等任务。如有参考资料,应事先通过 miaoda_write_reference 写入 reference 目录。修改已有项目时必须先调 list_projects 获取 project_id。返回结构化 JSON(status/appId/finalText/output/meta)。`,parameters:{type:`object`,properties:{generation_request:{type:`string`,description:`生成指令(如'做一个学习网站,包含章节、题库、测验')`},project_id:{type:`string`,description:`本地项目目录名,仅允许小写字母、数字和短横线`},target:{type:`string`,description:`飞书消息推送目标(如 user:ou_xxx)`}},required:[`generation_request`,`project_id`]},async execute(e,i){try{let e=J(i.generation_request),o=Y(i.project_id),s=Z(i.target),l=g(t,o);n.mkdirSync(l,{recursive:!0});let u=await H(l)?`工作目录下有 reference/ 目录,包含参考资料,请先查阅再开始工作。\n\n${e}`:e,d=r.join(l,`.agent`);if(!n.existsSync(d)){let e=_(t);if(n.existsSync(e))try{n.symlinkSync(e,d)}catch{}}let f=[],p,m=!1,h=null,v=new AbortController,y=setTimeout(()=>v.abort(),c);try{for await(let e of P({cwd:l,task:u,signal:v.signal})){if(`logId`in e&&e.logId&&f.push(e.logId),a.verbose&&s)switch(e.type){case`app_created`:K(s,`应用已创建(appId: ${e.appId})`);break;case`failed`:K(s,`处理失败: ${e.error}`);break}e.type===`completed`&&(h={appId:e.appId,finalText:e.finalText}),e.type===`failed`&&(p=e.error)}}catch{}clearTimeout(y),m=v.signal.aborted;let b=null;try{b=JSON.parse(n.readFileSync(r.join(l,`.spark`,`meta.json`),`utf8`))}catch{}let x={status:m?`timeout`:p?`error`:`ok`,project_id:o,appId:h?.appId??b?.appId??null,finalText:h?.finalText??b?.finalText,output:h?.finalText??b?.finalText,logIds:f.length?f:void 0,meta:b};return m?x.error=`执行超时(${c/1e3}秒)`:p&&(x.error=p),{content:[{type:`text`,text:JSON.stringify(x,null,2)}]}}catch(e){return{content:[{type:`text`,text:JSON.stringify({status:`error`,error:e instanceof Error?e.message:String(e)})}]}}}}),e.registerService({id:`miaoda-coding-config`,async start(e){a=G(e)},async stop(){}})}};export{re as default};
|
package/package.json
CHANGED
|
@@ -15,36 +15,61 @@ user-invocable: false
|
|
|
15
15
|
收到用户请求后,先判断:
|
|
16
16
|
|
|
17
17
|
- **纯建站/改站请求**(如"做一个计算器"、"改一下颜色")→ 直接派活给妙搭
|
|
18
|
-
-
|
|
19
|
-
|
|
20
|
-
复合请求的正确做法:
|
|
21
|
-
1. 主 agent 自己完成搜索/分析/整理
|
|
22
|
-
2. `brief` 写纯生成指令,例如:`"基于以下内容生成学习网站"`
|
|
23
|
-
3. `context_materials` 放整理好的内容摘要
|
|
24
|
-
4. 然后再调 sessions_spawn 派活
|
|
18
|
+
- **对话中有需要传递给 code agent 的上下文**(之前讨论的调研结果、用户偏好、素材等)→ 先调 `miaoda_write_reference` 写入参考资料,再派活
|
|
19
|
+
- **复合请求**(如"先搜集 XX 资料,再生成网站")→ 主 agent 先完成研究,**整理为摘要+关键原文引用**,调 `miaoda_write_reference` 写入,再派活
|
|
25
20
|
|
|
26
21
|
**错误做法**:把"先搜集再生成"的整个需求原样丢给妙搭——妙搭不会搜索,只会基于给定内容生成页面。
|
|
27
22
|
|
|
23
|
+
### 写入参考资料
|
|
24
|
+
|
|
25
|
+
用 `miaoda_write_reference` 将上下文写入项目的 reference 目录:
|
|
26
|
+
|
|
27
|
+
参数:
|
|
28
|
+
- `project_id`: 项目 ID
|
|
29
|
+
- `category`: `"research"`(调研结果)、`"design"`(设计要求)、`"feedback"`(用户反馈)
|
|
30
|
+
- `content`: Markdown 格式的参考资料(摘要+关键原文引用)
|
|
31
|
+
- `filename`: 可选,自定义文件名
|
|
32
|
+
- `mode`: `"append"`(默认,追加)或 `"replace"`(替换该 category 全部内容,用于用户反悔/调整)
|
|
33
|
+
|
|
34
|
+
整理原则:
|
|
35
|
+
- 结构化摘要:提炼对话中的关键结论、决策、需求
|
|
36
|
+
- 保留关键原文:用户的原话、重要数据、具体要求原样引用
|
|
37
|
+
- 不要把整段对话历史塞进去,提炼有价值的信息
|
|
38
|
+
|
|
39
|
+
### 调用者边界
|
|
40
|
+
|
|
41
|
+
| Tool | 谁调 |
|
|
42
|
+
|------|------|
|
|
43
|
+
| `miaoda_write_reference` | 主 agent(non-subagent 会话) |
|
|
44
|
+
| `miaoda_coding` | subagent |
|
|
45
|
+
| `miaoda_check_progress` | 主 agent(non-subagent 会话) |
|
|
46
|
+
| `list_projects` | 主 agent 或 subagent |
|
|
47
|
+
|
|
28
48
|
## 派活
|
|
29
49
|
|
|
30
50
|
通过 `sessions_spawn` 派给 subagent。**只传以下三个参数,不要传任何其他参数**(不要传 streamTo、sandbox、stream 等):
|
|
31
51
|
|
|
32
52
|
- `runtime`: `"subagent"`
|
|
33
53
|
- `mode`: `"run"`
|
|
34
|
-
- `task`:
|
|
54
|
+
- `task`: 按下面模板填写
|
|
35
55
|
|
|
36
56
|
如果 sessions_spawn 返回错误,去掉多余参数后重试,**绝对不要 fallback 到自己写代码**。
|
|
37
57
|
|
|
38
58
|
**创建新项目:**
|
|
59
|
+
|
|
60
|
+
1. 如有上下文需要传递,先调 `miaoda_write_reference`
|
|
61
|
+
2. 调 `sessions_spawn`,task 内容:
|
|
62
|
+
|
|
39
63
|
```
|
|
40
64
|
调用 miaoda_coding tool,参数:
|
|
41
|
-
-
|
|
42
|
-
- context_materials: "<如有前置研究结果,放在这里(可选)>"
|
|
65
|
+
- generation_request: "<生成指令>"
|
|
43
66
|
- project_id: "<project_id>"
|
|
44
67
|
- name: "<面向人类可读的应用名称>"
|
|
45
68
|
- description: "<根据用户需求整理的一句话简介,单行,80 字以内>"
|
|
46
69
|
- target: "user:<sender_id>"
|
|
47
70
|
|
|
71
|
+
如果 reference/ 目录已有参考资料,tool 会自动提示 code agent 查阅。
|
|
72
|
+
|
|
48
73
|
tool 会返回结构化 JSON(status/appId/finalText/output 等)。
|
|
49
74
|
|
|
50
75
|
如果结果里有 `output`,announce 给主 agent 时优先带 `output` 原文,不要自己改写预览链接,不要提前拼 `mode=sidebar-semi`。
|
|
@@ -53,13 +78,17 @@ tool 会返回结构化 JSON(status/appId/finalText/output 等)。
|
|
|
53
78
|
```
|
|
54
79
|
|
|
55
80
|
**修改已有项目:**
|
|
81
|
+
|
|
82
|
+
1. 如有新反馈/调整,先调 `miaoda_write_reference`(category="feedback",mode 按需选 append 或 replace)
|
|
83
|
+
2. 调 `sessions_spawn`,task 内容:
|
|
84
|
+
|
|
56
85
|
```
|
|
57
86
|
你只能调用以下两个 tool,按顺序执行,不得使用任何其他 tool(exec、ls、read 等均禁止):
|
|
58
87
|
|
|
59
88
|
1. 调用 list_projects tool,无需任何参数。
|
|
60
89
|
2. 从返回的 projects 数组中找到与用户需求匹配的项目,取其 project_id。
|
|
61
90
|
3. 调用 miaoda_coding tool,参数:
|
|
62
|
-
-
|
|
91
|
+
- generation_request: "<修改要求>"
|
|
63
92
|
- project_id: "<上一步取到的 project_id>"
|
|
64
93
|
- target: "user:<sender_id>"
|
|
65
94
|
|
|
@@ -79,16 +108,17 @@ tool 会返回结构化 JSON(status/appId/finalText/output 等)。
|
|
|
79
108
|
|
|
80
109
|
## 你(主 agent)的行为
|
|
81
110
|
|
|
82
|
-
1. 读完这个 skill
|
|
83
|
-
2.
|
|
84
|
-
3.
|
|
85
|
-
4. **不要**
|
|
86
|
-
5.
|
|
111
|
+
1. 读完这个 skill 后,判断是否需要写参考资料,需要则调 `miaoda_write_reference`
|
|
112
|
+
2. 调 sessions_spawn
|
|
113
|
+
3. 回复用户"交给妙搭了,稍等"
|
|
114
|
+
4. **不要** 调 sessions_history、subagents、或任何 poll 操作
|
|
115
|
+
5. **不要** 自己执行任何命令
|
|
116
|
+
6. subagent announce 回来后,**只发一条消息**,包含:
|
|
87
117
|
- 简短总结(做了什么、包含哪些内容)
|
|
88
|
-
- 如果结果里有预览链接(`output` / `finalText` 中提取),补上 `mode=sidebar-semi` query
|
|
118
|
+
- 如果结果里有预览链接(`output` / `finalText` 中提取),补上 `mode=sidebar-semi` query 参数后,以加粗 Markdown 链接格式 `**[<url>](<url>)**` 放在总结里
|
|
89
119
|
- 如果没找到预览链接,就按无链接结果回复,不要猜
|
|
90
120
|
- **严禁发多条消息**:不要先发链接再发总结,不要发完总结再补链接
|
|
91
121
|
- 不要提系统、子任务、announce、subagent 等内部细节
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
122
|
+
7. `miaoda_check_progress` 只用于用户主动问进度时使用,绝对不要根据 `appId`、进度文本或固定域名模式自己猜测预览链接
|
|
123
|
+
8. 如果 subagent completion event 还没到,即使进度里出现"处理完成",也只能回复进度状态,不能提前发最终链接
|
|
124
|
+
9. **不要** 调 message tool 自己推送消息,所有回复通过正常对话投递
|