@lark-apaas/openclaw-extension-miaoda-coding 1.0.8 → 1.0.10

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 CHANGED
@@ -1,10 +1,10 @@
1
- import{createRequire as e}from"node:module";import{spawn as t}from"node:child_process";import n,{readFileSync as r}from"node:fs";import i,{dirname as a,join as o,resolve as s}from"node:path";import{appendFile as c,mkdir as l,readFile as u,readdir as d,rm as f,writeFile as p}from"node:fs/promises";import{fileURLToPath as m}from"node:url";var h=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports),g=e(import.meta.url);function _(){return s(process.env.OPENCLAW_STATE_DIR||process.cwd())}function v(e){return o(e,`.agent`)}function y(e,t){return o(e,t)}function b(e){let t=typeof e.workspaceDir==`string`&&e.workspaceDir!==``,n=typeof e.agentDir==`string`&&e.agentDir!==``;if(!t||!n)throw Error(`resolveToolPaths: workspaceDir and agentDir are required in tool factory context`);return{workspaceDir:e.workspaceDir,agentDir:e.agentDir,agentId:e.agentId,sessionKey:e.sessionKey}}var x=h(((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:()=>m,parseJWTTokenWithVerify:()=>h,registerPlatformPlugin:()=>T,resolvePlatformBaseURL:()=>w}),t.exports=c(l);var u=g(`crypto`),d=1800*1e3,f=60,p={alg:`HS256`,typ:`JWT`};function m(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(p)),a=v(JSON.stringify(r));return`${i}.${a}.${_(`${i}.${a}`,t.secretKey)}`}function h(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=m(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}}}))();let S;function C(){if(S)return S;try{let e=s(a(m(import.meta.url)),`..`,`package.json`);S=JSON.parse(r(e,`utf-8`))}catch(e){console.warn(`[miaoda-coding] failed to read package.json: ${e instanceof Error?e.message:e}`),S={}}return S}function w(){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}}const T=new Set([`k_st_ec_400002683`]);var E=class extends Error{userFacing=!0;constructor(e,t){super(e),this.statusCode=t,this.name=`UserFacingError`}};function D(e){return!!e&&typeof e==`object`&&!Array.isArray(e)&&Object.values(e).every(e=>typeof e==`string`)}function O(...e){for(let t of e)if(typeof t==`string`&&t)return t}async function*k(e){let t=new TextDecoder,n=``,r;for await(let i of e){n+=t.decode(i,{stream:!0});let e=n.split(`
1
+ import{createRequire as e}from"node:module";import{spawn as t}from"node:child_process";import n,{readFileSync as r}from"node:fs";import i,{dirname as a,join as o,resolve as s}from"node:path";import{appendFile as c,mkdir as l,readFile as u,readdir as d,rm as f,writeFile as p}from"node:fs/promises";import{fileURLToPath as m}from"node:url";var h=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports),g=e(import.meta.url);function _(){return s(process.env.OPENCLAW_STATE_DIR||process.cwd())}function v(e){return o(e,`.agent`)}function y(e,t){return o(e,t)}function b(e){let t=typeof e.workspaceDir==`string`&&e.workspaceDir!==``,n=typeof e.agentDir==`string`&&e.agentDir!==``;if(!t||!n)throw Error(`resolveToolPaths: workspaceDir and agentDir are required in tool factory context`);return{workspaceDir:e.workspaceDir,agentDir:e.agentDir,agentId:e.agentId,sessionKey:e.sessionKey}}var x=h(((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,FileTokenProvider:()=>w,HttpClient:()=>L,HttpError:()=>P,generateJWTToken:()=>m,parseJWTTokenWithVerify:()=>h,registerPlatformPlugin:()=>k,resolvePlatformBaseURL:()=>O}),t.exports=c(l);var u=g(`crypto`),d=1800*1e3,f=60,p={alg:`HS256`,typ:`JWT`};function m(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(p)),a=v(JSON.stringify(r));return`${i}.${a}.${_(`${i}.${a}`,t.secretKey)}`}function h(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=m(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=g(`fs`),S=300*1e3,C=[`/home/gem/workspace/.force/openclaw/miaoda-provider-key`],w=class{cache=new Map;paths;refreshBeforeMs;constructor(e){this.paths=e?.paths??C,this.refreshBeforeMs=e?.refreshBeforeMs??S}getToken(){let e=Date.now();for(let t of this.paths){let n=this.cache.get(t);if(n&&n.expiresAtMs-e>this.refreshBeforeMs)return{token:n.token,accessKey:n.accessKey};let r=this.readAndParse(t);if(r&&(this.cache.set(t,r),r.expiresAtMs>e))return{token:r.token,accessKey:r.accessKey};if(n&&n.expiresAtMs>e)return{token:n.token,accessKey:n.accessKey}}return null}clearCache(){this.cache.clear()}readAndParse(e){try{let t=(0,x.readFileSync)(e,`utf-8`).trim();if(!t)return null;let n=this.decodePayload(t);return n?.exp?{token:t,accessKey:n.access_key??``,expiresAtMs:n.exp*1e3}:null}catch{return null}}decodePayload(e){try{let t=e.split(`.`);if(t.length!==3)return null;let n=(4-(t[1].length%4||4))%4,r=`${t[1]}${`=`.repeat(n)}`.replace(/-/g,`+`).replace(/_/g,`/`);return JSON.parse(Buffer.from(r,`base64`).toString(`utf8`))}catch{return null}}},T=`FORCE_AUTHN_INNERAPI_DOMAIN`,E=`FORCE_AUTHN_ACCESS_KEY`,D=`FORCE_AUTHN_ACCESS_SECRET`;function O(e){if(!e?.enabled)return e?.baseURL;if(e.baseURL)return e.baseURL;let t=e.domainEnv||T,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 k(e,t){if(!t.enabled)return;A(`platform.defaultClaims`,t.defaultClaims);let n;t.tokenProvider?.type===`file`&&(n=new w({paths:t.tokenProvider.paths,refreshBeforeMs:t.refreshBeforeMs}));let r=t.accessKeyEnv||E,i=t.secretKeyEnv||D,a=t.accessKey??(process.env[r]||``),o=new b({accessKey:a,secretKey:t.secretKey??(process.env[i]||``),expireTimeMs:t.expireTimeMs??d,refreshBeforeMs:t.refreshBeforeMs});return e.request.use(e=>{if(n){let t=n.getToken();if(t){let n={...e.headers,Authorization:`Bearer ${t.token}`,"x-api-key":t.accessKey};return{...e,headers:n}}}A(`request.platformAuth.customClaims`,e.platformAuth?.customClaims);let r={...t.defaultClaims||{},...e.platformAuth?.customClaims||{},access_key:a},i=o.getToken(r),s={...e.headers,Authorization:`Bearer ${i}`,"x-api-key":a};return{...e,headers:s}}),o}function A(e,t){if(t&&Object.prototype.hasOwnProperty.call(t,`access_key`))throw Error(`${e} \u4E0D\u5141\u8BB8\u8BBE\u7F6E access_key`)}var j=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 M(e){if(typeof e!=`object`||!e)return!1;let t=Object.getPrototypeOf(e);return t===Object.prototype||t===null}function N(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 P=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=F(n),Object.setPrototypeOf(this,e.prototype)}};function F(e){let{headers:t,...n}=e;return{...n,headers:I(t)}}function I(e){if(!e)return;let t=N(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 L=class{defaultConfig;securityConfig;interceptors={request:new j,response:new j};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=O(t)),k(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 M(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 M(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 M(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=N(this.defaultConfig.headers),n=N(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=N(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 P(e,a);return this.runResponseInterceptors(Promise.reject(t))}return this.runResponseInterceptors(Promise.resolve(e))}catch(e){clearTimeout(l);let t=e instanceof P?e:new P(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}}}))();let S;function C(){if(S)return S;try{let e=s(a(m(import.meta.url)),`..`,`package.json`);S=JSON.parse(r(e,`utf-8`))}catch(e){console.warn(`[miaoda-coding] failed to read package.json: ${e instanceof Error?e.message:e}`),S={}}return S}function w(){let e=process.env.FORCE_AUTHN_INNERAPI_DOMAIN;if(!e)throw Error(`studio-client: 缺少环境变量,需要: FORCE_AUTHN_INNERAPI_DOMAIN`);return{apiUrl:e}}const T=new Set([`k_st_ec_400002683`]);var E=class extends Error{userFacing=!0;constructor(e,t){super(e),this.statusCode=t,this.name=`UserFacingError`}};function D(e){return!!e&&typeof e==`object`&&!Array.isArray(e)&&Object.values(e).every(e=>typeof e==`string`)}function O(...e){for(let t of e)if(typeof t==`string`&&t)return t}async function*k(e){let t=new TextDecoder,n=``,r;for await(let i of e){n+=t.decode(i,{stream:!0});let e=n.split(`
2
2
 
3
3
  `);n=e.pop();for(let t of e){let e=t.split(`
4
- `),n=e.find(e=>e.startsWith(`id:`));n&&(r=n.slice(3).trim());let i=e.find(e=>e.startsWith(`data:`));if(!i)continue;let a=i.slice(5).trim();if(a){if(a===`[DONE]`){yield{data:null,id:r,done:!0};return}try{yield{data:JSON.parse(a),id:r,done:!1}}catch{}}}}}var A=class{#e;#t;constructor(){this.#t=w();let e=C().headers;this.#e=new x.HttpClient({baseURL:this.#t.apiUrl,timeout:9e5,...process.env.FORCE_FRAMEWORK_ENVIRONMENT!==`online`&&D(e)?{headers:e}:{},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),e.agentId&&(n.agentId=e.agentId);let r;try{r=await this.#e.post(t,n,{headers:{"Content-Type":`application/json`}})}catch(e){if(e instanceof x.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 T.has(a.status_code)?new E(a.error_msg,a.status_code):Error(`createSubApp 服务错误 (logID: ${i}): ${a.error_msg}`);let o=O(a.appID,a.appId,a.app_id,a.data?.appID,a.data?.appId,a.data?.app_id),s=O(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={"Content-Type":`application/json`,Accept:`text/event-stream`};e.lastEventID&&(r[`Last-Event-ID`]=e.lastEventID);let i;try{i=await this.#e.post(t,n,{headers:r})}catch(e){if(e instanceof x.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:i.headers?.get?.(`x-tt-logid`)??``},yield*k(i.body)}};const j=`.spark`,M=`meta.json`;function N(e){return o(e,j,M)}async function P(e){try{return JSON.parse(await u(N(e),`utf8`))}catch{return null}}async function F(e){for(let t of[o(e,`..`,`..`,j,M),o(e,`..`,j,M)])try{return JSON.parse(await u(t,`utf8`))}catch{}return null}async function I(e,t){await l(o(e,j),{recursive:!0});let n=await P(e),r={...n??{},...t};if(r.features||=[],!n){let t=await F(e);if(t)for(let[e,n]of Object.entries(t))e in r||(r[e]=n)}await p(N(e),JSON.stringify(r,null,2),`utf8`)}function L(e){let t=e;return t?.data?.message??t?.Data?.Message??null}async function ee(e){let t=e.maxReconnect??2,n=e.maxTotalReconnect??10,r=e.reconnectDelayMs??1500,i,a=0,o=0,s=0,c,l;for(;;){let u=!1,d=!1,f=!1,p=i;try{for await(let t of e.createStream(p)){if(e.signal?.aborted)return{totalEvents:s,reconnectCount:o,lastEventId:i,finalText:c,logId:l,success:!1,error:`cancelled`};let n=t;if(n?.__meta){l=n.logId||l;continue}let r=n;if(r.id&&(i=r.id),r.id&&p&&r.id===p)continue;if(r.done)break;s++,a>0&&(a=0);let d=L(r.data);if(d?.type===`metadata`&&d?.status!==`in_progress`&&(u=!0),d?.role===`assistant`&&d?.type===`message`&&d?.status===`completed`&&typeof d?.content==`string`&&d.content&&(c=d.content),e.onEvent&&e.onEvent(r,{lastEventId:i,reconnectCount:o})===!1){f=!0;break}}}catch(e){if(!u){let t=e instanceof Error?e.message:String(e);if(/\b4[0-9]{2}\b/.test(t)||t.includes(`业务错误`))return{totalEvents:s,reconnectCount:o,lastEventId:i,finalText:c,logId:l,success:!1,error:t};d=!0}}if(f)break;if(s===0&&!u&&!d)return{totalEvents:s,reconnectCount:o,lastEventId:i,finalText:c,logId:l,success:!1,error:`stream returned no events`};if(u)break;if(d||i){if(a++,o++,a<=t&&o<=n&&i){if(e.onReconnect?.(o,i),await new Promise(t=>{let n=setTimeout(t,r);e.signal&&e.signal.addEventListener(`abort`,()=>{clearTimeout(n),t()},{once:!0})}),e.signal?.aborted)return{totalEvents:s,reconnectCount:o,lastEventId:i,finalText:c,logId:l,success:!1,error:`cancelled`};continue}let u=`stream disconnected, reconnect exhausted`;return e.onFailed?.(u),{totalEvents:s,reconnectCount:o,lastEventId:i,finalText:c,logId:l,success:!1,error:u}}break}return e.onComplete?.({totalEvents:s,reconnectCount:o,lastEventId:i}),{totalEvents:s,reconnectCount:o,lastEventId:i,finalText:c,logId:l,success:!0}}const R=`.spark`,z=new Intl.DateTimeFormat(`en-CA`,{timeZone:`Asia/Shanghai`,year:`numeric`,month:`2-digit`,day:`2-digit`,hour:`2-digit`,minute:`2-digit`,second:`2-digit`,hour12:!1,timeZoneName:`longOffset`});function B(e){return o(e,R,`progress.txt`)}function V(){let e=Object.fromEntries(z.formatToParts(new Date).filter(e=>e.type!==`literal`).map(e=>[e.type,e.value])),t=e.timeZoneName===`GMT`?`+00:00`:e.timeZoneName.replace(/^GMT/,``);return`${e.year}-${e.month}-${e.day}T${e.hour}:${e.minute}:${e.second}${t}`}function H(e){let t=[],n=``,r=``,i=``,a=e.includes(`## 已完成`)||e.includes(`## 当前任务`),o=!1,s=!a;for(let c of e.split(`
5
- `)){let e=c.trim();if(a){if(e===`## 已完成`){o=!0,s=!1;continue}if(e.startsWith(`## 当前任务`)){s=!0,o=!1;continue}if(e===``)continue}if(o&&e)t.push(e);else if(s&&e){let t=e.match(/^\[([^\]]+)\]\s*(.*)/);if(t){let e=t[2];i=t[1],r=e,!a&&e.startsWith(`需求: `)&&(n=e.slice(4))}else n||=e}}return{history:t,taskDesc:n,lastStatus:r,lastTimestamp:i}}async function U(e,t){await l(o(e,R),{recursive:!0});let n=B(e),r=[];try{let e=await u(n,`utf8`);if(e.trim()){let t=H(e);if(r.push(...t.history),t.taskDesc){let e=t.lastTimestamp||V(),n;n=t.lastStatus.startsWith(`失败:`)?t.lastStatus:t.lastStatus===`处理完成`?`成功`:t.lastStatus||`未知状态`,r.push(`[${e}] ${t.taskDesc} — ${n}`)}}}catch{}await p(n,`## 已完成\n\n${r.length?r.join(`
4
+ `),n=e.find(e=>e.startsWith(`id:`));n&&(r=n.slice(3).trim());let i=e.find(e=>e.startsWith(`data:`));if(!i)continue;let a=i.slice(5).trim();if(a){if(a===`[DONE]`){yield{data:null,id:r,done:!0};return}try{yield{data:JSON.parse(a),id:r,done:!1}}catch{}}}}}var A=class{#e;#t;constructor(){this.#t=w();let e=C().headers;this.#e=new x.HttpClient({baseURL:this.#t.apiUrl,timeout:9e5,...process.env.FORCE_FRAMEWORK_ENVIRONMENT!==`online`&&D(e)?{headers:e}:{},platform:{enabled:!0,tokenProvider:{type:`file`}}})}async createSubApp(e={}){let t=`${this.#t.apiUrl}/api/v1/studio/innerapi/openclaw/sub_app/create`,n={};e.message&&(n.message=e.message),e.agentId&&(n.agentId=e.agentId);let r;try{r=await this.#e.post(t,n,{headers:{"Content-Type":`application/json`}})}catch(e){if(e instanceof x.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 T.has(a.status_code)?new E(a.error_msg,a.status_code):Error(`createSubApp 服务错误 (logID: ${i}): ${a.error_msg}`);let o=O(a.appID,a.appId,a.app_id,a.data?.appID,a.data?.appId,a.data?.app_id),s=O(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={"Content-Type":`application/json`,Accept:`text/event-stream`};e.lastEventID&&(r[`Last-Event-ID`]=e.lastEventID);let i;try{i=await this.#e.post(t,n,{headers:r})}catch(e){if(e instanceof x.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:i.headers?.get?.(`x-tt-logid`)??``},yield*k(i.body)}};const j=`.spark`,M=`meta.json`;function N(e){return o(e,j,M)}async function P(e){try{return JSON.parse(await u(N(e),`utf8`))}catch{return null}}async function F(e){for(let t of[o(e,`..`,`..`,j,M),o(e,`..`,j,M)])try{return JSON.parse(await u(t,`utf8`))}catch{}return null}async function I(e,t){await l(o(e,j),{recursive:!0});let n=await P(e),r={...n??{},...t};if(r.features||=[],!n){let t=await F(e);if(t)for(let[e,n]of Object.entries(t))e in r||(r[e]=n)}await p(N(e),JSON.stringify(r,null,2),`utf8`)}function L(e){let t=e;return t?.data?.message??t?.Data?.Message??null}async function ee(e){let t=e.maxReconnect??2,n=e.maxTotalReconnect??10,r=e.reconnectDelayMs??1500,i,a=0,o=0,s=0,c,l;for(;;){let u=!1,d=!1,f=!1,p=i;try{for await(let t of e.createStream(p)){if(e.signal?.aborted)return{totalEvents:s,reconnectCount:o,lastEventId:i,finalText:c,logId:l,success:!1,error:`cancelled`};let n=t;if(n?.__meta){l=n.logId||l;continue}let r=n;if(r.id&&(i=r.id),r.id&&p&&r.id===p)continue;if(r.done)break;s++,a>0&&(a=0);let d=L(r.data);if(d?.type===`metadata`&&d?.status!==`in_progress`&&(u=!0),d?.role===`assistant`&&d?.type===`message`&&d?.status===`completed`&&typeof d?.content==`string`&&d.content&&(c=d.content),e.onEvent&&e.onEvent(r,{lastEventId:i,reconnectCount:o})===!1){f=!0;break}}}catch(e){if(!u){let t=e instanceof Error?e.message:String(e);if(/\b4[0-9]{2}\b/.test(t)||t.includes(`业务错误`))return{totalEvents:s,reconnectCount:o,lastEventId:i,finalText:c,logId:l,success:!1,error:t};d=!0}}if(f)break;if(s===0&&!u&&!d)return{totalEvents:s,reconnectCount:o,lastEventId:i,finalText:c,logId:l,success:!1,error:`stream returned no events`};if(u)break;if(d||i){if(a++,o++,a<=t&&o<=n&&i){if(e.onReconnect?.(o,i),await new Promise(t=>{let n=setTimeout(t,r);e.signal&&e.signal.addEventListener(`abort`,()=>{clearTimeout(n),t()},{once:!0})}),e.signal?.aborted)return{totalEvents:s,reconnectCount:o,lastEventId:i,finalText:c,logId:l,success:!1,error:`cancelled`};continue}let u=`stream disconnected, reconnect exhausted`;return e.onFailed?.(u),{totalEvents:s,reconnectCount:o,lastEventId:i,finalText:c,logId:l,success:!1,error:u}}break}return e.onComplete?.({totalEvents:s,reconnectCount:o,lastEventId:i}),{totalEvents:s,reconnectCount:o,lastEventId:i,finalText:c,logId:l,success:!0}}let R=``,z=0;function te(e){try{let t=ne(e);if(!t)return null;let n=t.replace(/\s*\(.*\)$/,``).replace(/\s*:.*$/,``),r=Date.now();return n===R&&r-z<3e4?null:(R=n,z=r,t)}catch{return null}}function ne(e){let t=e.role,n=e.tool_calls,r=e.content;if(e.finish_reason,t===`assistant`&&n?.length)for(let e of n){let t=e?.function?.name??e?.name,n={};try{let t=e?.function?.arguments??e?.arguments;n=typeof t==`string`?JSON.parse(t):t??{}}catch{}if(t===`bash`){let e=String(n.command??``);if(e.includes(`init`))return`初始化项目`;if(e.includes(`db sql`))return`数据库操作`;if(e.includes(`deploy`))return`部署中`}}return t===`tool`&&typeof r==`string`&&(r.startsWith(`执行 run_shell_command 工具失败`)||r.startsWith(`command exitcode:`))?`失败: ${r.slice(0,200)+(r.length>200?`...`:``)}`:null}const B=`.spark`,re=new Intl.DateTimeFormat(`en-CA`,{timeZone:`Asia/Shanghai`,year:`numeric`,month:`2-digit`,day:`2-digit`,hour:`2-digit`,minute:`2-digit`,second:`2-digit`,hour12:!1,timeZoneName:`longOffset`});function V(e){return o(e,B,`progress.txt`)}function H(){let e=Object.fromEntries(re.formatToParts(new Date).filter(e=>e.type!==`literal`).map(e=>[e.type,e.value])),t=e.timeZoneName===`GMT`?`+00:00`:e.timeZoneName.replace(/^GMT/,``);return`${e.year}-${e.month}-${e.day}T${e.hour}:${e.minute}:${e.second}${t}`}function ie(e){let t=[],n=``,r=``,i=``,a=e.includes(`## 已完成`)||e.includes(`## 当前任务`),o=!1,s=!a;for(let c of e.split(`
5
+ `)){let e=c.trim();if(a){if(e===`## 已完成`){o=!0,s=!1;continue}if(e.startsWith(`## 当前任务`)){s=!0,o=!1;continue}if(e===``)continue}if(o&&e)t.push(e);else if(s&&e){let t=e.match(/^\[([^\]]+)\]\s*(.*)/);if(t){let e=t[2];i=t[1],r=e,!a&&e.startsWith(`需求: `)&&(n=e.slice(4))}else n||=e}}return{history:t,taskDesc:n,lastStatus:r,lastTimestamp:i}}async function ae(e,t){await l(o(e,B),{recursive:!0});let n=V(e),r=[];try{let e=await u(n,`utf8`);if(e.trim()){let t=ie(e);if(r.push(...t.history),t.taskDesc){let e=t.lastTimestamp||H(),n;n=t.lastStatus.startsWith(`失败:`)?t.lastStatus:t.lastStatus===`处理完成`?`成功`:t.lastStatus||`未知状态`,r.push(`[${e}] ${t.taskDesc} — ${n}`)}}}catch{}await p(n,`## 已完成\n\n${r.length?r.join(`
6
6
  `)+`
7
- `:``}\n## 当前任务\n\n${t}\n`,`utf8`)}async function W(e,t){await c(B(e),`[${V()}] ${t}\n`,`utf8`)}async function*te(e){let{cwd:t,task:n,agentId:r,signal:i}=e;try{let a=new A;await U(t,(e.requestText??n).slice(0,200)),await W(t,`已接收需求`),yield{type:`progress`,message:`已接收需求`};let o=await P(t),s=!o?.appId,c=V();if(s){let e;try{e=await a.createSubApp({message:n,agentId:r})}catch(e){let n=e instanceof Error?e.message:String(e),r=e instanceof E;try{await W(t,`失败: ${n}`)}catch{}yield{type:`failed`,error:n,phase:`create`,userFacing:r};return}let{appID:i,conversationID:s,logId:l}=e;o={appId:i,conversationId:s,createdAt:c,updatedAt:c},await I(t,o),await W(t,`应用已创建(appId: ${i})`),yield{type:`app_created`,appId:i,conversationId:s,logId:l}}await W(t,`正在处理`),yield{type:`progress`,message:`正在处理`};let l=!1,u=Date.now(),d=setInterval(()=>{W(t,`正在开发中(已 ${Math.floor((Date.now()-u)/6e4)} 分钟)`).catch(()=>{})},6e4),f;try{f=await ee({createStream:e=>a.streamChat({appID:o.appId,message:n,workDir:t,...e?{lastEventID:e}:{}}),onEvent:e=>{if(i?.aborted)return!1;let n=L(e.data),r=n?.delta?.type;!l&&n?.role===`assistant`&&(n?.status===`in_progress`||r===`text`)&&(l=!0,W(t,`正在输出结果`).catch(()=>{}))},onReconnect:(t,n)=>{e.onReconnect?.(t,n)},onFailed:n=>{e.onStreamFailed?.(n),W(t,`失败: ${n}`).catch(()=>{})},signal:i})}finally{clearInterval(d)}if(!f.success){let e=f.error?.includes(`disconnected`)||f.error?.includes(`no events`);yield{type:`failed`,error:f.error||`stream failed`,retryable:e};return}let{finalText:p,logId:m}=f;if(l&&(yield{type:`progress`,message:`正在输出结果`,logId:m||void 0}),i?.aborted){await W(t,`失败: cancelled`),yield{type:`failed`,error:`cancelled`};return}let h=V();await I(t,{...o,updatedAt:h,finalText:p}),await W(t,`处理完成`),yield{type:`completed`,appId:o.appId,logId:m||void 0,finalText:p}}catch(e){let n=e instanceof Error?e.message:String(e);try{await W(t,`失败: ${n}`)}catch{}yield{type:`failed`,error:n,logId:e?.logId}}}const G=[`research`,`design`,`feedback`];function ne(e){return e.replace(/[^a-zA-Z0-9\u4e00-\u9fff-]/g,`-`).replace(/-+/g,`-`).replace(/^-|-$/g,``).slice(0,60)||`reference`}async function re(e){let t;try{t=await d(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 ie(e){try{let t=await u(o(e,`reference`,`manifest.json`),`utf8`);return JSON.parse(t)}catch{return{updatedAt:new Date().toISOString(),files:[]}}}async function ae(e,t){await l(o(e,`reference`),{recursive:!0}),await p(o(e,`reference`,`manifest.json`),JSON.stringify(t,null,2),`utf8`)}async function oe(e){let{appCwd:t,category:n,content:r,filename:i,mode:a=`append`}=e,s=o(t,`reference`,n);if(a===`replace`)try{await f(s,{recursive:!0,force:!0})}catch{}await l(s,{recursive:!0});let c=await re(s),u=`${n}/${`${String(c).padStart(3,`0`)}-${ne(i??`reference`)}.md`}`,d=`reference/${u}`;await p(o(t,d),r,`utf8`);let m=await ie(t);return a===`replace`&&(m.files=m.files.filter(e=>e.category!==n)),m.files.push({path:u,category:n,writtenAt:new Date().toISOString()}),m.updatedAt=new Date().toISOString(),await ae(t,m),{relativePath:d,category:n,seq:c}}async function K(e){let t=o(e,`reference`),n;try{n=await d(t)}catch{return{totalFiles:0,categories:{}}}let r={},i=0;for(let e of n){if(!G.includes(e))continue;let n;try{n=(await d(o(t,e))).filter(e=>e.endsWith(`.md`)).sort()}catch{continue}n.length>0&&(r[e]={count:n.length,files:n},i+=n.length)}return{totalFiles:i,categories:r}}async function se(e){return(await K(e)).totalFiles>0}const ce=/^[a-z0-9][a-z0-9-]{0,63}$/,le=new Set([`.`,`..`,`con`,`nul`,`prn`,`aux`]);function q(e){let t=e?.config?.plugins?.entries?.[`openclaw-extension-miaoda-coding`]?.config??{};return{verbose:t?.verbose===!0,hooks:{allowPromptInjection:t?.hooks?.allowPromptInjection!==!1}}}function J(e,n){if(!e)return;let r=i.dirname(process.execPath),a=t(`openclaw`,[`message`,`send`,`--channel`,`feishu`,`--target`,e,`--message`,n],{stdio:[`ignore`,`ignore`,`pipe`],env:{...process.env,PATH:`${r}:${process.env.PATH||``}`}});a.stderr.on(`data`,()=>{}),a.on(`close`,()=>{}),a.unref()}function Y(e){return String(e??``).replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F]/g,``).replace(/\s+/g,` `).trim()}function ue(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 X(e){let t=Y(e);if(!Z(t))throw Error(`project_id 不合法: "${t}"`);return t}function Z(e){return ce.test(e)&&!le.has(e)}function de(e){if(e==null||e===``)return;let t=Y(e);if(!/^(user|chat|thread):\S+$/.test(t))throw Error(`target 不合法`);return t}function fe(e){try{return n.readFileSync(e,`utf8`).split(`
8
- `).map(e=>e.trim()).filter(Boolean)}catch{return[]}}function pe(e){return e.replace(/^\[[^\]]+\]\s*/,``)}function Q(e){return e.replace(/应用已创建(appId:\s*[^)]+)/g,`应用已创建`)}function $(e){return e.slice(-3).map(pe).map(Q).join(` | `)||`暂无进度记录`}function me(e){let t=$(e);return t.includes(`失败:`)?`failed`:t.includes(`处理完成`)?`completed`:t===`暂无进度记录`?`pending`:`running`}var he={id:`openclaw-extension-miaoda-coding`,name:`妙搭`,description:`妙搭制品创建与修改`,register(e){let t=`[miaoda-coding]`,r={info:n=>e.logger.info?.(`${t} ${n}`),warn:n=>e.logger.warn?.(`${t} ${n}`),error:n=>e.logger.error?.(`${t} ${n}`)},a={verbose:!1,hooks:{allowPromptInjection:!0}},o=new Set([`miaoda_coding`]),s=new Set([`miaoda_check_progress`]);e.on(`before_tool_call`,(e,t)=>{let n=e.toolName,i=t?.sessionKey??``,a=i.toLowerCase().split(`:`).includes(`subagent`);if(o.has(n)&&!a)return r.warn(`blocked ${n} from non-subagent session: ${i}`),{block:!0,blockReason:`${n} 只能由 subagent 调用。请通过 sessions_spawn(runtime="subagent", mode="run") 派活,不要直接调用此工具。读 miaoda-coding skill 了解完整流程。`};if(s.has(n)&&a)return r.warn(`blocked ${n} from subagent session: ${i}`),{block:!0,blockReason:`${n} 不能由 subagent 调用。subagent 不应直接操作参考资料或查询进度。`}}),e.on(`before_prompt_build`,(e,t)=>{if(q(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(e=>{let t=b(e);return{name:`list_projects`,label:`列出妙搭项目`,description:`列出已创建的妙搭项目,返回 project_id、appId、updatedAt、finalText、status 和进度摘要。修改已有项目时必须先调此工具。`,parameters:{type:`object`,properties:{}},async execute(){let e=[];try{e=n.readdirSync(t.workspaceDir,{withFileTypes:!0}).filter(e=>e.isDirectory()&&Z(e.name)).map(e=>{let r=i.join(t.workspaceDir,e.name,`.spark`,`meta.json`),a=i.join(t.workspaceDir,e.name,`.spark`,`progress.txt`),o=null;try{o=JSON.parse(n.readFileSync(r,`utf8`))}catch{}let s=fe(a);return{project_id:e.name,appId:o?.appId??null,updatedAt:o?.updatedAt??null,finalText:o?.finalText??null,status:me(s),progressSummary:$(s)}})}catch{}return{content:[{type:`text`,text:JSON.stringify({projects:e},null,2)}]}}}}),e.registerTool(e=>{let t=b(e);return{name:`miaoda_check_progress`,label:`查看妙搭项目进度`,description:`查看妙搭项目的生成进度。用户问"做到哪了"、"进度如何"、"好了吗"时调此工具。`,parameters:{type:`object`,properties:{project_id:{type:`string`,description:`项目目录名(从 list_projects 获取)`}},required:[`project_id`]},async execute(e,r){try{let e=X(r.project_id),a=i.join(t.workspaceDir,e,`.spark`,`progress.txt`),o=n.readFileSync(a,`utf8`).trim().split(`
7
+ `:``}\n## 当前任务\n\n${t}\n`,`utf8`)}async function U(e,t){await c(V(e),`[${H()}] ${t}\n`,`utf8`)}async function*oe(e){let{cwd:t,task:n,agentId:r,signal:i}=e;try{let a=new A,o=e.requestText??n;await ae(t,o.slice(0,200)+(o.length>200?`...`:``)),await U(t,`已接收需求`),yield{type:`progress`,message:`已接收需求`};let s=await P(t),c=!s?.appId,l=H();if(c){let e;try{e=await a.createSubApp({message:n,agentId:r})}catch(e){let n=e instanceof Error?e.message:String(e),r=e instanceof E;try{await U(t,`失败: ${n}`)}catch{}yield{type:`failed`,error:n,phase:`create`,userFacing:r};return}let{appID:i,conversationID:o,logId:c}=e;s={appId:i,conversationId:o,createdAt:l,updatedAt:l},await I(t,s),await U(t,`应用已创建(appId: ${i})`),yield{type:`app_created`,appId:i,conversationId:o,logId:c}}await U(t,`正在处理`),yield{type:`progress`,message:`正在处理`};let u=!1,d=Date.now(),f=setInterval(()=>{U(t,`正在开发中(已 ${Math.floor((Date.now()-d)/6e4)} 分钟)`).catch(()=>{})},6e4),p;try{p=await ee({createStream:e=>a.streamChat({appID:s.appId,message:n,workDir:t,...e?{lastEventID:e}:{}}),onEvent:e=>{if(i?.aborted)return!1;let n=L(e.data),r=n?.delta?.type;!u&&n?.role===`assistant`&&(n?.status===`in_progress`||r===`text`)&&(u=!0,U(t,`正在输出结果`).catch(()=>{}));try{if(n?.status===`completed`){let e=te(n);e&&U(t,e).catch(()=>{})}}catch{}},onReconnect:(t,n)=>{e.onReconnect?.(t,n)},onFailed:n=>{e.onStreamFailed?.(n),U(t,`失败: ${n}`).catch(()=>{})},signal:i})}finally{clearInterval(f)}if(!p.success){let e=p.error?.includes(`disconnected`)||p.error?.includes(`no events`);yield{type:`failed`,error:p.error||`stream failed`,retryable:e};return}let{finalText:m,logId:h}=p;if(u&&(yield{type:`progress`,message:`正在输出结果`,logId:h||void 0}),i?.aborted){await U(t,`失败: cancelled`),yield{type:`failed`,error:`cancelled`};return}let g=H();await I(t,{...s,updatedAt:g,finalText:m}),m&&await U(t,`Agent 回复: ${m.slice(0,200)+(m.length>200?`...`:``)}`),await U(t,`处理完成`),yield{type:`completed`,appId:s.appId,logId:h||void 0,finalText:m}}catch(e){let n=e instanceof Error?e.message:String(e);try{await U(t,`失败: ${n}`)}catch{}yield{type:`failed`,error:n,logId:e?.logId}}}const W=[`research`,`design`,`feedback`];function se(e){return e.replace(/[^a-zA-Z0-9\u4e00-\u9fff-]/g,`-`).replace(/-+/g,`-`).replace(/^-|-$/g,``).slice(0,60)||`reference`}async function ce(e){let t;try{t=await d(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 le(e){try{let t=await u(o(e,`reference`,`manifest.json`),`utf8`);return JSON.parse(t)}catch{return{updatedAt:new Date().toISOString(),files:[]}}}async function ue(e,t){await l(o(e,`reference`),{recursive:!0}),await p(o(e,`reference`,`manifest.json`),JSON.stringify(t,null,2),`utf8`)}async function de(e){let{appCwd:t,category:n,content:r,filename:i,mode:a=`append`}=e,s=o(t,`reference`,n);if(a===`replace`)try{await f(s,{recursive:!0,force:!0})}catch{}await l(s,{recursive:!0});let c=await ce(s),u=`${n}/${`${String(c).padStart(3,`0`)}-${se(i??`reference`)}.md`}`,d=`reference/${u}`;await p(o(t,d),r,`utf8`);let m=await le(t);return a===`replace`&&(m.files=m.files.filter(e=>e.category!==n)),m.files.push({path:u,category:n,writtenAt:new Date().toISOString()}),m.updatedAt=new Date().toISOString(),await ue(t,m),{relativePath:d,category:n,seq:c}}async function G(e){let t=o(e,`reference`),n;try{n=await d(t)}catch{return{totalFiles:0,categories:{}}}let r={},i=0;for(let e of n){if(!W.includes(e))continue;let n;try{n=(await d(o(t,e))).filter(e=>e.endsWith(`.md`)).sort()}catch{continue}n.length>0&&(r[e]={count:n.length,files:n},i+=n.length)}return{totalFiles:i,categories:r}}async function fe(e){return(await G(e)).totalFiles>0}const pe=/^[a-z0-9][a-z0-9-]{0,63}$/,me=new Set([`.`,`..`,`con`,`nul`,`prn`,`aux`]);function K(e){let t=e?.config?.plugins?.entries?.[`openclaw-extension-miaoda-coding`]?.config??{};return{verbose:t?.verbose===!0,hooks:{allowPromptInjection:t?.hooks?.allowPromptInjection!==!1}}}function q(e,n){if(!e)return;let r=i.dirname(process.execPath),a=t(`openclaw`,[`message`,`send`,`--channel`,`feishu`,`--target`,e,`--message`,n],{stdio:[`ignore`,`ignore`,`pipe`],env:{...process.env,PATH:`${r}:${process.env.PATH||``}`}});a.stderr.on(`data`,()=>{}),a.on(`close`,()=>{}),a.unref()}function J(e){return String(e??``).replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F]/g,``).replace(/\s+/g,` `).trim()}function he(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=J(e);if(!X(t))throw Error(`project_id 不合法: "${t}"`);return t}function X(e){return pe.test(e)&&!me.has(e)}function ge(e){if(e==null||e===``)return;let t=J(e);if(!/^(user|chat|thread):\S+$/.test(t))throw Error(`target 不合法`);return t}function _e(e){try{return n.readFileSync(e,`utf8`).split(`
8
+ `).map(e=>e.trim()).filter(Boolean)}catch{return[]}}function Z(e){return e.replace(/^\[[^\]]+\]\s*/,``)}function Q(e){return e.replace(/应用已创建(appId:\s*[^)]+)/g,`应用已创建`)}function $(e){return e.slice(-3).map(Z).map(Q).join(` | `)||`暂无进度记录`}function ve(e){let t=$(e);return t.includes(`失败:`)?`failed`:t.includes(`处理完成`)?`completed`:t===`暂无进度记录`?`pending`:`running`}var ye={id:`openclaw-extension-miaoda-coding`,name:`妙搭`,description:`妙搭制品创建与修改`,register(e){let t=`[miaoda-coding]`,r={info:n=>e.logger.info?.(`${t} ${n}`),warn:n=>e.logger.warn?.(`${t} ${n}`),error:n=>e.logger.error?.(`${t} ${n}`)},a={verbose:!1,hooks:{allowPromptInjection:!0}},o=new Set([`miaoda_coding`]),s=new Set([`miaoda_check_progress`]);e.on(`before_tool_call`,(e,t)=>{let n=e.toolName,i=t?.sessionKey??``,a=i.toLowerCase().split(`:`).includes(`subagent`);if(o.has(n)&&!a)return r.warn(`blocked ${n} from non-subagent session: ${i}`),{block:!0,blockReason:`${n} 只能由 subagent 调用。请通过 sessions_spawn(runtime="subagent", mode="run") 派活,不要直接调用此工具。读 miaoda-coding skill 了解完整流程。`};if(s.has(n)&&a)return r.warn(`blocked ${n} from subagent session: ${i}`),{block:!0,blockReason:`${n} 不能由 subagent 调用。subagent 不应直接操作参考资料或查询进度。`}}),e.on(`before_prompt_build`,(e,t)=>{if(K(t).hooks.allowPromptInjection)return{prependSystemContext:[`【硬规则 — 妙搭】`,`当用户要求创建或修改应用、网页、页面时,必须通过 sessions_spawn(runtime="subagent", mode="run") 交给妙搭。`,`读 miaoda-coding skill 后严格按指示操作。只传 runtime、mode、task 三个参数,不要传 streamTo/sandbox 等。`,`绝对不要自己写代码或用 exec 生成文件。如果 sessions_spawn 调用本身返回错误,去掉多余参数后重试。如果 subagent 执行失败需要重试,generation_request 填"继续"(不要重复发完整需求),详见 skill 中的重试流程。`,`如果对话中有需要传递给 code agent 的上下文(研究结果、用户偏好等),先调 miaoda_write_reference 写入 reference 目录,再 spawn。用户反悔或需要替换之前的上下文时,使用 miaoda_write_reference 的 mode=replace。妙搭只做制品生成,不做研究。`,`不要用 sessions_history 读取子 agent 的 session,查进度用 miaoda_check_progress tool。`,`subagent 完成后只发一条消息,严禁发多条。不要调 message tool。如果 result 的 hint 说预览链接已自动发送,则只发纯文字总结,不要再带任何链接;否则附上预览链接,格式用加粗 Markdown 链接 **[url](url)**,不要发裸 URL。`].join(` `)}}),e.registerTool(e=>{let t=b(e);return{name:`list_projects`,label:`列出妙搭项目`,description:`列出已创建的妙搭项目,返回 project_id、appId、updatedAt、finalText、status 和进度摘要。修改已有项目时必须先调此工具。`,parameters:{type:`object`,properties:{}},async execute(){let e=[];try{e=n.readdirSync(t.workspaceDir,{withFileTypes:!0}).filter(e=>e.isDirectory()&&X(e.name)).map(e=>{let r=i.join(t.workspaceDir,e.name,`.spark`,`meta.json`),a=i.join(t.workspaceDir,e.name,`.spark`,`progress.txt`),o=null;try{o=JSON.parse(n.readFileSync(r,`utf8`))}catch{}let s=_e(a);return{project_id:e.name,appId:o?.appId??null,updatedAt:o?.updatedAt??null,finalText:o?.finalText??null,status:ve(s),progressSummary:$(s)}})}catch{}return{content:[{type:`text`,text:JSON.stringify({projects:e},null,2)}]}}}}),e.registerTool(e=>{let t=b(e);return{name:`miaoda_check_progress`,label:`查看妙搭项目进度`,description:`查看妙搭项目的生成进度。用户问"做到哪了"、"进度如何"、"好了吗"时调此工具。`,parameters:{type:`object`,properties:{project_id:{type:`string`,description:`项目目录名(从 list_projects 获取)`}},required:[`project_id`]},async execute(e,r){try{let e=Y(r.project_id),a=i.join(t.workspaceDir,e,`.spark`,`progress.txt`),o=n.readFileSync(a,`utf8`).trim().split(`
9
9
  `).filter(Boolean),s=null;try{s=JSON.parse(n.readFileSync(i.join(t.workspaceDir,e,`.spark`,`meta.json`),`utf8`))}catch{}let c=!!s?.finalText,l=o.slice(-20).map(Q).join(`
10
- `);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:r.project_id,message:`暂无进度记录`,delivery_ready:!1,finalTextAvailable:!1,note:`miaoda_check_progress 只用于查看进度,不能根据 appId 或进度文本推导预览链接;最终链接只以 subagent completion result 中的 output/finalText 为准。`})}]}}}}}),e.registerTool(e=>{let t=b(e);return{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=X(n.project_id);if(!G.includes(n.category))throw Error(`category 不合法,必须是 ${G.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=y(t.workspaceDir,e),o=await oe({appCwd:a,category:n.category,content:r,filename:n.filename,mode:i}),s=await K(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(e=>{let t=b(e);return{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,o){try{let e=ue(o.generation_request),s=X(o.project_id),l=de(o.target),u=y(t.workspaceDir,s);n.mkdirSync(u,{recursive:!0}),r.info(`miaoda_coding called (project=${s}, isNew=${!n.existsSync(i.join(u,`.spark`,`meta.json`))})`);let d=await se(u)?`工作目录下有 reference/ 目录,包含参考资料,请先查阅再开始工作。\n\n${e}`:e,f=i.join(u,`.agent`);if(!n.existsSync(f)){let e=v(_());if(n.existsSync(e))try{n.symlinkSync(e,f)}catch{}}let p=[],m,h=!1,g=!1,b=null,x=new AbortController,S=setTimeout(()=>x.abort(),c);try{for await(let n of te({cwd:u,task:d,requestText:e,agentId:t.agentId,signal:x.signal,onReconnect:(e,t)=>{r.warn(`stream reconnect (project=${s}, attempt=${e}, lastEventId=${t??`none`})`)},onStreamFailed:e=>{r.error(`stream failed (project=${s}): ${e}`)}})){if(`logId`in n&&n.logId&&p.push(n.logId),a.verbose&&l)switch(n.type){case`app_created`:J(l,`应用已创建(appId: ${n.appId})`);break;case`failed`:J(l,`处理失败: ${n.error}`);break}if(n.type===`completed`&&(b={appId:n.appId,finalText:n.finalText}),n.type===`failed`&&(m=n.error,h=n.retryable??!1,n.phase===`create`)){r.error(`createSubApp failed (project=${s}): ${n.error}`),clearTimeout(S),l&&J(l,n.userFacing?n.error:`createSubApp 失败: ${n.error}`);let e=n.userFacing?{status:`error`,project_id:s,appId:null,meta:null,hint:`创建失败,详情已通过飞书发送给用户,不要重复发送错误内容`}:{status:`error`,project_id:s,appId:null,error:n.error,meta:null,hint:`createSubApp 失败,错误已通过飞书发送。根据 error 内容用通俗语言告诉用户具体原因,不要重试`};return{content:[{type:`text`,text:JSON.stringify(e)}]}}}}catch{}clearTimeout(S),g=x.signal.aborted;let C=null;try{C=JSON.parse(n.readFileSync(i.join(u,`.spark`,`meta.json`),`utf8`))}catch{}let w={status:g?`timeout`:m?`error`:`ok`,project_id:s,appId:b?.appId??C?.appId??null,finalText:b?.finalText??C?.finalText,output:b?.finalText??C?.finalText,logIds:p.length?p:void 0,meta:C};if(r.info(`miaoda_coding done (project=${s}, status=${w.status}, logIds=${p.join(`,`)})`),g?(w.error=`执行超时(${c/1e3}秒)`,w.retryable=!0,w.hint=`执行超时,任务可能仍在后台运行。建议先调用 miaoda_check_progress 查看进度,再决定是否重试`):m&&(w.error=m,h&&(w.retryable=!0,w.hint=`网络连接中断,任务可能已部分完成。建议先调用 miaoda_check_progress 查看当前状态,再决定下一步`)),l&&w.status===`ok`){let e=C?.appUrl;if(r.info(`delivery check (project=${s}, target=${l}, appUrl=${e??`missing`})`),typeof e==`string`&&e.trim()){let t=e.includes(`?`)?`&`:`?`,n=`${e.trim()}${t}mode=sidebar-semi`;J(l,`**[${n}](${n})**`),r.info(`delivery sent (project=${s}, url=${n})`),w.hint=`预览链接已自动发送给用户,announce 时不要再带预览链接`}else r.warn(`delivery skipped: no appUrl in meta.json (project=${s})`)}return{content:[{type:`text`,text:JSON.stringify(w,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=q(e)},async stop(){}})}};export{he as default};
10
+ `);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:r.project_id,message:`暂无进度记录`,delivery_ready:!1,finalTextAvailable:!1,note:`miaoda_check_progress 只用于查看进度,不能根据 appId 或进度文本推导预览链接;最终链接只以 subagent completion result 中的 output/finalText 为准。`})}]}}}}}),e.registerTool(e=>{let t=b(e);return{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(!W.includes(n.category))throw Error(`category 不合法,必须是 ${W.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=y(t.workspaceDir,e),o=await de({appCwd:a,category:n.category,content:r,filename:n.filename,mode:i}),s=await G(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(e=>{let t=b(e);return{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,群聊用 chat:oc_xxx`}},required:[`generation_request`,`project_id`]},async execute(e,o){try{let e=he(o.generation_request),s=Y(o.project_id),l=ge(o.target),u=y(t.workspaceDir,s);n.mkdirSync(u,{recursive:!0}),r.info(`miaoda_coding called (project=${s}, isNew=${!n.existsSync(i.join(u,`.spark`,`meta.json`))})`);let d=await fe(u)?`工作目录下有 reference/ 目录,包含参考资料,请先查阅再开始工作。\n\n${e}`:e,f=i.join(u,`.agent`);if(!n.existsSync(f)){let e=v(_());if(n.existsSync(e))try{n.symlinkSync(e,f)}catch{}}let p=[],m,h=!1,g=!1,b=null,x=new AbortController,S=setTimeout(()=>x.abort(),c);try{for await(let n of oe({cwd:u,task:d,requestText:e,agentId:t.agentId,signal:x.signal,onReconnect:(e,t)=>{r.warn(`stream reconnect (project=${s}, attempt=${e}, lastEventId=${t??`none`})`)},onStreamFailed:e=>{r.error(`stream failed (project=${s}): ${e}`)}})){if(`logId`in n&&n.logId&&p.push(n.logId),a.verbose&&l)switch(n.type){case`app_created`:q(l,`应用已创建(appId: ${n.appId})`);break;case`failed`:q(l,`处理失败: ${n.error}`);break}if(n.type===`completed`&&(b={appId:n.appId,finalText:n.finalText}),n.type===`failed`&&(m=n.error,h=n.retryable??!1,n.phase===`create`)){r.error(`createSubApp failed (project=${s}): ${n.error}`),clearTimeout(S),l&&q(l,n.userFacing?n.error:`createSubApp 失败: ${n.error}`);let e=n.userFacing?{status:`error`,project_id:s,appId:null,meta:null,hint:`创建失败,详情已通过飞书发送给用户,不要重复发送错误内容`}:{status:`error`,project_id:s,appId:null,error:n.error,meta:null,hint:`createSubApp 失败,错误已通过飞书发送。根据 error 内容用通俗语言告诉用户具体原因,不要重试`};return{content:[{type:`text`,text:JSON.stringify(e)}]}}}}catch{}clearTimeout(S),g=x.signal.aborted;let C=null;try{C=JSON.parse(n.readFileSync(i.join(u,`.spark`,`meta.json`),`utf8`))}catch{}let w={status:g?`timeout`:m?`error`:`ok`,project_id:s,appId:b?.appId??C?.appId??null,finalText:b?.finalText??C?.finalText,output:b?.finalText??C?.finalText,logIds:p.length?p:void 0,meta:C};if(r.info(`miaoda_coding done (project=${s}, status=${w.status}, logIds=${p.join(`,`)})`),g?(w.error=`执行超时(${c/1e3}秒)`,w.retryable=!0,w.hint=`执行超时,任务可能仍在后台运行。建议先调用 miaoda_check_progress 查看进度,再决定是否重试`):m&&(w.error=m,h&&(w.retryable=!0,w.hint=`网络连接中断,任务可能已部分完成。建议先调用 miaoda_check_progress 查看当前状态,再决定下一步`)),l&&w.status===`ok`){let e=C?.appUrl;if(r.info(`delivery check (project=${s}, target=${l}, appUrl=${e??`missing`})`),typeof e==`string`&&e.trim()){let t=e.includes(`?`)?`&`:`?`,n=`${e.trim()}${t}mode=sidebar-semi`;q(l,`**[${n}](${n})**`),r.info(`delivery sent (project=${s}, url=${n})`),w.hint=`预览链接已自动发送给用户,announce 时不要再带预览链接`}else r.warn(`delivery skipped: no appUrl in meta.json (project=${s})`)}return{content:[{type:`text`,text:JSON.stringify(w,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=K(e)},async stop(){}})}};export{ye as default};
package/package.json CHANGED
@@ -1,9 +1,10 @@
1
1
  {
2
2
  "name": "@lark-apaas/openclaw-extension-miaoda-coding",
3
- "version": "1.0.8",
3
+ "version": "1.0.10",
4
4
  "description": "妙搭 Coding Agent — 通过 AI 创建和修改妙搭应用",
5
5
  "type": "module",
6
6
  "scripts": {
7
+ "prepack": "tsdown",
7
8
  "build": "tsdown",
8
9
  "build:dev": "MINIFY=false tsdown",
9
10
  "test": "vitest run",
@@ -24,7 +25,7 @@
24
25
  ]
25
26
  },
26
27
  "devDependencies": {
27
- "@lark-apaas/http-client": "^0.1.2",
28
+ "@lark-apaas/http-client": "^0.1.3",
28
29
  "@vitest/coverage-v8": "^4.0.18",
29
30
  "openclaw": "^2026.3.2",
30
31
  "tsdown": "^0.21.0",
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: miaoda-coding
3
- description: 用户要求创建或修改应用、网页、页面、PPT、幻灯片、演示文稿,或提到"妙搭""vibe coding"时适用。
3
+ description: 通过妙搭开发插件来创建简单网页,例如信息可视化展示(图表、看板、简报、报表等)、PPT/演示文稿、简单工具或应用原型。用户提到"妙搭""vibe coding"时也适用。注意:CRM/ERP/OA等管理系统、电商/教育/医疗等业务平台、AI能力应用、定时任务、知识库等复杂需求不适用本skill;纯文字创作(写文章/脚本/文案等)以及纯数据分析(用户只要分析结论、未要求做报告/看板/可视化)不要包装成应用。
4
4
  user-invocable: false
5
5
  ---
6
6
 
@@ -8,9 +8,50 @@ user-invocable: false
8
8
 
9
9
  **重要:创建或修改应用、网页、PPT 等制品的执行环节,必须通过 sessions_spawn 交给妙搭。绝对不要自己动手写代码。**
10
10
 
11
+ ## ⚠️ 能力边界判断(必须首先执行)
12
+
13
+ 妙搭开发插件可以做**四类事情**,收到用户请求后,**必须先判断是否属于这四类**,再决定是派活还是静默退出本 skill。退出时不要向用户描述任何限制,主 agent 会直接接管处理。
14
+
15
+ ### 支持的四类场景 → 派活给妙搭
16
+
17
+ 1. **信息可视化展示(最核心的场景)**:基于用户提供的数据做图表、看板、简报、报表、数据大屏、趋势分析图、对比展示等呈现。只要需求偏向"把信息/数据用可视化的方式展示出来",都应该走妙搭。包括但不限于:行业简报、经营数据看板、项目进度展示、竞品对比、方案汇报页、数据报告展示页等
18
+ 2. **简单应用原型**:用于展示产品思路或交互方案的原型页面,不是一个真正投入使用的系统(如落地页、概念演示、方案展示)
19
+ 3. **PPT / 演示文稿**:幻灯片、课件、汇报材料
20
+ 4. **简单工具**:有页面、也有一些基础的数据增删改查能力,但功能单一、不涉及复杂业务流程,重点是和 Agent 协同的工具,人和 Agent 都可以操作背后的数据
21
+
22
+ ### 不适用的场景 → 静默退出本 skill
23
+
24
+ 遇到以下需求时,**不要派活给妙搭**,直接静默退出本 skill,让主 agent 自行规划。**也不要把不适用的需求降级为"做个原型"**——用户要的是 CRM 就是 CRM,不要自作主张变成做 CRM 原型。退出时不要向用户解释妙搭的限制或不适用原因,主 agent 会直接处理:
25
+
26
+ | 类别 | 识别关键词 |
27
+ |------|-----------|
28
+ | **复杂管理系统** | "XX管理系统/平台"(客户管理、人力管理、财务管理、库存管理、生产管理、设备管理、物流管理、车队管理、物业管理、门店管理、医疗管理、安全管理等)、"CRM/ERP/OA/HRM/MES/WMS/进销存"、"全流程管理/一站式管理/端到端"、"多角色登录/权限管理/角色管理"、"工单/审批流/工作流" |
29
+ | **复杂业务平台** | "电商平台/交易平台/商城"、"教育/学习/培训/考试平台"、"医疗/健康管理平台"、"社交/社区平台"、"内容/直播运营平台"、"招聘平台" |
30
+ | **定时任务/自动化/推送** | "每日/每天/每周/每月"、"定时/定期执行"、"自动采集/抓取/同步/更新"、"实时监控/监测/预警"、"爬虫/爬取"、"推送通知/实时推送/按需推送/订阅推送"、"聚合订阅" |
31
+ | **AI 智能能力** | "AI分析/生成/推荐/预测/识别"、"AI辅助/AI驱动/AI赋能"、"智能匹配/评分/批改"、"AI对话/聊天/陪伴"、"预测分析/预测报告"、"AI工作台" |
32
+ | **纯文字创作** | "写文章/脚本/文案/小说/诗歌/演讲稿/邮件"、"撰写/创作/润色/改写/翻译"、"初稿写作/大纲生成/选题推荐"。**只要用户要的是纯文字产出,就直接以文字回复,不要做成网页/应用/PPT** |
33
+ | **纯数据分析** | "分析这份数据"、"帮我看看这些数字"、"算一下/统计一下"、"得出结论/给出建议"等——用户**只要分析结论**、没有明确要求"做个报告/看板/页面/可视化"时。**直接用文字或表格回答分析结果即可,不要自作主张包装成应用** |
34
+ | **知识库/智能问答** | "知识库"、"文档问答"、"智能搜索"、"知识图谱" |
35
+ | **复杂 PDF 生成** | "生成 PDF"、"导出 PDF"、"转 PDF"、"思维导图 PDF" |
36
+ | **需要外部数据源** | "实时行情/价格/股价"、"获取最新数据"、"接入 API"、"全网搜索/新闻聚合"、"历史开奖数据/历史行情数据" |
37
+ | **抢票/秒杀** | "抢票"、"秒杀"、"自动抢/购"、"监控库存" |
38
+ | **部署/运维** | "部署实例/一键部署"、"运维/服务器管理"、"环境配置/实例管理" |
39
+ | **智能体/Bot 配置** | "配置智能体"、"搭建智能体"、"机器人配置" |
40
+ | **数据对接/桥接** | "数据源连接/桥接"、"API 对接"、"数据库连接" |
41
+ | **AI 图片生成** | "AI生成图片/一键生成图片"、"文生图/图生图"、"AI绘画/AI作画" |
42
+
43
+ ### 边界模糊时的判断原则
44
+
45
+ 问自己三个问题(**按顺序判断,第 1 条优先级最高**):
46
+ 1. **"需求的主题是否命中上面的不适用场景表?"** — 只要主题属于表中类别(CRM、ERP、AI 应用、电商平台等),无论用户怎么描述("做一个 CRM"、"做个简单 CRM"、"帮我搭个客户管理系统"),都应退出。不适用场景的判断优先于"是否只是原型"的判断
47
+ 2. **"这是一个简单工具/原型/展示页,还是一个真正的业务系统?"** — 如果用户期望的是一个可以真正投入日常使用、涉及多角色/多流程/复杂业务逻辑的系统,妙搭开发插件做不了
48
+ 3. **"这个需求依赖 AI 推理、定时任务、外部数据源吗?"** — 如果依赖,妙搭开发插件做不了
49
+
50
+ 三个问题任一答"是"→ 静默退出本 skill,主 agent 直接处理(不要解释妙搭开发插件为什么做不了)
51
+
11
52
  ## 判断是否需要先做前置工作
12
53
 
13
- 妙搭只负责**生成制品**(网页、应用、PPT),不负责搜索、分析、总结等研究工作。
54
+ 妙搭开发插件只负责**生成制品**(网页、应用、PPT),不负责搜索、分析、总结等研究工作。
14
55
 
15
56
  收到用户请求后,先判断:
16
57
 
@@ -53,20 +94,25 @@ user-invocable: false
53
94
  - `mode`: `"run"`
54
95
  - `task`: 按下面模板填写
55
96
 
56
- 如果 sessions_spawn 返回错误,去掉多余参数后重试,**绝对不要 fallback 到自己写代码**。
97
+ 如果 sessions_spawn 调用本身返回错误(如参数错误),去掉多余参数后重试,**绝对不要 fallback 到自己写代码**。
57
98
 
58
99
  **创建新项目:**
59
100
 
60
101
  1. 如有上下文需要传递,先调 `miaoda_write_reference`
61
102
  2. 调 `sessions_spawn`,task 内容:
62
103
 
104
+ **generation_request 编写原则**:
105
+ - 忠实传递用户的功能需求,不要自行添加技术选型(如数据库方案、存储方式、第三方 API 等)
106
+ - code agent 运行在独立沙箱中,不具备你(openclaw)的插件和工具能力(如飞书多维表格、飞书文档等),不要推荐你自己的能力给它
107
+ - 技术方案由 code agent 根据平台内置能力自行决定
108
+
63
109
  ```
64
110
  调用 miaoda_coding tool,参数:
65
111
  - generation_request: "<生成指令>"
66
112
  - project_id: "<project_id>"
67
113
  - name: "<面向人类可读的应用名称>"
68
114
  - description: "<根据用户需求整理的一句话简介,单行,80 字以内>"
69
- - target: "user:<sender_id>"
115
+ - target: 根据消息来源判断——群聊中用 "chat:<chat_id>"(如 chat:oc_xxx),单聊中用 "user:<sender_id>"(如 user:ou_xxx)
70
116
 
71
117
  如果 reference/ 目录已有参考资料,tool 会自动提示 code agent 查阅。
72
118
 
@@ -90,7 +136,7 @@ tool 返回的 JSON 里如果有 `hint` 字段,严格按 hint 指示行事。
90
136
  3. 调用 miaoda_coding tool,参数:
91
137
  - generation_request: "<修改要求>"
92
138
  - project_id: "<上一步取到的 project_id>"
93
- - target: "user:<sender_id>"
139
+ - target: 根据消息来源判断——群聊中用 "chat:<chat_id>"(如 chat:oc_xxx),单聊中用 "user:<sender_id>"(如 user:ou_xxx)
94
140
 
95
141
  tool 会返回结构化 JSON(status/appId/finalText/output 等)。
96
142
 
@@ -100,6 +146,7 @@ tool 返回的 JSON 里如果有 `hint` 字段,严格按 hint 指示行事。
100
146
  ```
101
147
 
102
148
  `<sender_id>` 从消息上下文的 sender_id 字段获取(格式如 `ou_xxx`)。
149
+ `<chat_id>` 从消息上下文的 chat_id / ChatType / To 字段判断:ChatType 为 `"group"` 时取 chat_id(格式如 `oc_xxx`)。
103
150
 
104
151
  `<project_id>` 仅允许小写字母、数字和短横线,创建新项目时根据用户需求生成,例如:
105
152
  - "帮我做一个 hello world 网页" → `hello-world-webpage`
@@ -108,17 +155,19 @@ tool 返回的 JSON 里如果有 `hint` 字段,严格按 hint 指示行事。
108
155
 
109
156
  ## 你(主 agent)的行为
110
157
 
111
- 1. 读完这个 skill 后,判断是否需要写参考资料,需要则调 `miaoda_write_reference`
112
- 2. sessions_spawn
113
- 3. 回复用户"交给妙搭了,稍等"
114
- 4. **不要** 调 sessions_history、subagents、或任何 poll 操作
115
- 5. subagent announce 回来后,**只发一条总结消息**:简短总结做了什么
116
- - **不需要发预览链接** 预览链接由插件自动投递给用户,你不需要从 output/finalText 中提取
158
+ 1. 读完这个 skill 后,**首先执行能力边界判断**,如果不在支持范围内,**立即静默退出本 skill**,交由主 agent 自行规划处理。**禁止输出任何关于妙搭开发插件限制、能力边界、不适用原因的描述**——用户不需要知道妙搭开发插件不能做什么,主 agent 直接按自身能力响应用户需求即可
159
+ 2. 确认在能力范围内后,判断是否需要写参考资料,需要则调 `miaoda_write_reference`
160
+ 3. 调 sessions_spawn
161
+ 4. 回复用户"交给妙搭了,稍等"
162
+ 5. **不要** sessions_history、subagents、或任何 poll 操作
163
+ 6. subagent announce 回来后,**只发一条总结消息**:简短总结做了什么,并附上预览链接
164
+ - 预览链接获取方式:读 `workspace/<project_id>/.spark/meta.json` 中的 `appUrl` 字段,拼上 `?mode=sidebar-semi`(如已有 query 参数则用 `&mode=sidebar-semi`)
165
+ - 如果 `meta.json` 中没有 `appUrl`,说明可能未部署成功,参考「查看执行详情」章节了解情况
117
166
  - **严禁发多条消息**
118
167
  - 不要提系统、子任务、announce、subagent 等内部细节
119
- 6. `miaoda_check_progress` 用于两种场景:(a) 用户主动问进度时,(b) 任务失败且结果中有 `hint` 建议查进度时
120
- 7. **不要** 调 message tool 自己推送消息,所有回复通过正常对话投递
121
- 8. **不要** 自己写代码或用 exec/write 生成 HTML/JS/CSS 文件
168
+ 7. `miaoda_check_progress` 用于两种场景:(a) 用户主动问进度时,(b) 任务失败且结果中有 `hint` 建议查进度时
169
+ 8. **不要** 调 message tool 自己推送消息,所有回复通过正常对话投递
170
+ 9. **不要** 自己写代码或用 exec/write 生成 HTML/JS/CSS 文件
122
171
 
123
172
  ## 处理失败和异常
124
173
 
@@ -128,6 +177,7 @@ subagent 返回的结果 JSON 中可能包含 `status: "error"` 或 `status: "ti
128
177
  - 如果 progress 显示已完成 → 正常回复结果
129
178
  - 如果 progress 显示仍在运行 → 告诉用户"还在处理中,稍后再查"
130
179
  - 如果 progress 显示失败 → 问用户是否要重试
180
+ - **重试时**:走**修改已有项目**流程,`generation_request` 填 `"继续"`。feida-ai 的 conversation 中已有完整上下文(需求 + 之前的代码 + 失败日志),发"继续"即可让 Agent 接着上次的进度工作。**禁止**用创建模板重复发送完整的原始需求——这会导致 Agent 看到重复的需求消息,浪费 token 并造成混乱
131
181
  2. **`hint` 包含"createSubApp 失败"**:createSubApp 是创建应用的前置步骤,失败原因可能是用户额度不足、权限不够、或服务异常等。根据 `error` 字段的具体内容用通俗语言告诉用户(如"额度用完了"、"没有权限"、"服务暂时不可用"),**不要重试,不要调 miaoda_check_progress**
132
182
  3. **`retryable: false` 或无 `retryable` 字段**:直接告诉用户失败了,附上错误信息,问用户怎么处理
133
183
  4. **结果里没有预览链接**(`hint` 提到"未检测到预览链接"):调 `miaoda_check_progress` 查看最新状态,可能链接还没生成
@@ -136,3 +186,23 @@ subagent 返回的结果 JSON 中可能包含 `status: "error"` 或 `status: "ti
136
186
  - 不要在用户不知情的情况下自动重试——先告诉用户情况,等用户确认
137
187
  - 不要把 `retryable`、`hint`、`logId` 等内部字段暴露给用户
138
188
  - 不要说"stream disconnected"、"reconnect exhausted"等技术术语
189
+
190
+ ## 查看执行详情
191
+
192
+ 项目执行信息位于 workspace/<project_id>/.spark/ 下,可直接读取:
193
+
194
+ - `meta.json`:应用元信息(appId、appUrl 等),用于获取预览链接
195
+ - `progress.txt`:关键节点进度日志(轻量,适合快速了解当前状态)
196
+ - `messages.jsonl`:完整对话记录,每行一条 OpenAI Messages 格式的 JSON(适合失败诊断、了解完整执行过程)
197
+
198
+ 注意:messages.jsonl 可能较长,不要一次性读取全部内容。
199
+ - 查看最新进度:读 progress.txt 或 tail messages.jsonl 最后几行
200
+ - 诊断失败原因:读 messages.jsonl 最后 10-20 行,定位失败前后的操作
201
+ - 了解完整过程:分段读取,避免超出上下文限制
202
+
203
+ ## 判定任务是否完成
204
+
205
+ subagent announce 后,读 workspace/<project_id>/.spark/progress.txt 了解实际执行情况。
206
+ 需要更多细节时读 messages.jsonl。
207
+
208
+ 基于对实际情况的了解,自行判断下一步行动。
@@ -0,0 +1,244 @@
1
+ ---
2
+ name: miaoda-database-skill
3
+ description: 创建数据库、建表、执行 SQL、查询数据、查看表结构。当用户需要数据库、数据表、数据持久化、PostgreSQL 操作时使用。通过 miaoda-data-cli db 子命令(db init / db sql / db schema)完成数据库初始化、DDL/DML/SELECT 执行和表结构查看。
4
+ ---
5
+ # Database Skill
6
+
7
+ 本技能指导 Agent 通过 `miaoda-data-cli db` 命令操作数据库,包括:
8
+ - **db sql**:执行 SQL 语句(DDL/SELECT/DML)
9
+ - **db schema**:查看数据库表结构(默认 DDL,`--json` 输出结构化 JSON)
10
+
11
+ ### 使用方式
12
+
13
+ 推荐通过 `npx -y` 直接运行,无需预先安装:
14
+
15
+ ```bash
16
+ npx -y @lark-apaas/miaoda-data-cli db sql "SELECT 1"
17
+ ```
18
+
19
+ ## 通用约定
20
+
21
+ ### 错误处理
22
+
23
+ 所有命令失败时退出码为 **1**,错误信息输出到 **stderr**,格式为 JSON:
24
+
25
+ ```json
26
+ {"statusCode": 2, "message": "SQL 执行失败", "detail": "[k_dl_1300002] ERROR: relation \"xxx\" does not exist"}
27
+ ```
28
+
29
+ | statusCode | 含义 | 是否重试 | 触发场景 |
30
+ |------------|------|----------|---------|
31
+ | 1 | 语法/参数错误 | 否 | SQL 语法错误、参数缺失、表名不合法 |
32
+ | 2 | 执行失败 / 业务错误 | 否 | 表不存在、插入失败、权限不足等 |
33
+ | 3 | 服务异常 | 是,最多 1 次 | 网络错误、数据库暂时不可用 |
34
+
35
+ **处理策略**:
36
+ - statusCode 1 → 检查语法,修正后重新执行
37
+ - statusCode 2 → 读取 `detail` 字段定位原因,修正后重新执行
38
+ - 若 message 包含"数据库未初始化",说明尚未执行 `db init`,需先执行 `db init` 初始化数据库后再重试
39
+ - statusCode 3 → 重试一次,仍失败则告知用户
40
+
41
+ ### 输出约定
42
+
43
+ - `db sql`:默认输出表格到 **stdout**,`--json` 输出结构化 JSON
44
+ - `db schema`:默认输出 DDL 到 **stdout**,`--json` 输出表结构 JSON
45
+
46
+ ---
47
+
48
+ ## 命令:db init
49
+
50
+ 初始化数据库。**首次使用 `db sql` / `db schema` 前必须执行**。此命令是幂等的,重复执行不会报错。
51
+
52
+ ### 格式
53
+
54
+ ```bash
55
+ npx -y @lark-apaas/miaoda-data-cli db init
56
+ ```
57
+
58
+ ### 输出
59
+
60
+ ```
61
+ Database initialized.
62
+ ```
63
+
64
+ ### 典型工作流
65
+
66
+ ```bash
67
+ # 1. 初始化数据库
68
+ npx -y @lark-apaas/miaoda-data-cli db init
69
+
70
+ # 2. 建表
71
+ npx -y @lark-apaas/miaoda-data-cli db sql "CREATE TABLE note (id UUID PRIMARY KEY DEFAULT gen_random_uuid(), title VARCHAR(255) NOT NULL)"
72
+
73
+ # 3. 查看表结构
74
+ npx -y @lark-apaas/miaoda-data-cli db schema note
75
+ ```
76
+
77
+ ---
78
+
79
+ ## 命令:db sql
80
+
81
+ 执行 SQL 语句并返回结果。
82
+
83
+ ### 格式
84
+
85
+ ```bash
86
+ npx -y @lark-apaas/miaoda-data-cli db sql "<SQL 语句>"
87
+
88
+ # 加 --json 输出结构化 JSON
89
+ npx -y @lark-apaas/miaoda-data-cli db sql --json "<SQL 语句>"
90
+ ```
91
+
92
+ ### 示例
93
+
94
+ ```bash
95
+ # DDL
96
+ npx -y @lark-apaas/miaoda-data-cli db sql "
97
+ CREATE TABLE note (id UUID PRIMARY KEY DEFAULT gen_random_uuid(), title VARCHAR(255) NOT NULL, content TEXT);
98
+ COMMENT ON TABLE note IS 'note table';
99
+ CREATE INDEX idx_note_title ON note(title);
100
+ "
101
+
102
+ # SELECT
103
+ npx -y @lark-apaas/miaoda-data-cli db sql "SELECT title, content FROM note LIMIT 10"
104
+
105
+ # INSERT
106
+ npx -y @lark-apaas/miaoda-data-cli db sql "INSERT INTO note (title, content) VALUES ('hello', 'world')"
107
+ ```
108
+
109
+ ### 默认输出(表格格式)
110
+
111
+ **SELECT**:
112
+ ```
113
+ title | content
114
+ ------+--------
115
+ hello | world
116
+ (1 row)
117
+ ```
118
+
119
+ **DML**:
120
+ ```
121
+ 1 row affected
122
+ ```
123
+
124
+ **DDL**:
125
+ ```
126
+ DDL executed successfully
127
+ ```
128
+
129
+ ### --json 输出
130
+
131
+ **单条语句**:
132
+
133
+ SELECT — 直接输出查询结果的 JSON 数组:
134
+ ```json
135
+ [
136
+ {"title": "hello", "content": "world"}
137
+ ]
138
+ ```
139
+
140
+ DML — 输出影响行数:
141
+ ```json
142
+ {"affectedRows": 1}
143
+ ```
144
+
145
+ DDL — 输出成功标识:
146
+ ```json
147
+ {"status":"ok"}
148
+ ```
149
+
150
+ **多条语句**:输出带 index/type 的结构化数组:
151
+ ```json
152
+ [
153
+ {"index": 0, "type": "DDL", "status": "ok"},
154
+ {"index": 1, "type": "DML", "affectedRows": 1},
155
+ {"index": 2, "type": "SELECT", "rows": [{"id": "...", "name": "test"}], "rowCount": 1}
156
+ ]
157
+ ```
158
+
159
+ ### 返回值处理
160
+
161
+ - **DDL**:默认输出确认文本,`--json` 输出 `{"status":"ok"}`,退出码 0 即成功
162
+ - **SELECT**:默认输出表格,`--json` 直接输出数据 JSON 数组,可直接解析使用
163
+ - **DML**:默认输出影响行数文本,`--json` 输出 `{"affectedRows": N}`
164
+
165
+ ---
166
+
167
+ ## 命令:db schema
168
+
169
+ 查看数据库表结构。默认输出 DDL(CREATE TABLE 语句),`--json` 输出表结构 JSON。支持多表名。
170
+
171
+ ### 格式
172
+
173
+ ```bash
174
+ # 所有表 DDL
175
+ npx -y @lark-apaas/miaoda-data-cli db schema
176
+
177
+ # 指定表 DDL
178
+ npx -y @lark-apaas/miaoda-data-cli db schema <table_name>
179
+
180
+ # 多表 DDL
181
+ npx -y @lark-apaas/miaoda-data-cli db schema <table1> <table2>
182
+
183
+ # 表结构 JSON
184
+ npx -y @lark-apaas/miaoda-data-cli db schema --json
185
+ npx -y @lark-apaas/miaoda-data-cli db schema <table_name> --json
186
+ ```
187
+
188
+ ### 示例
189
+
190
+ ```bash
191
+ # 所有表 DDL
192
+ npx -y @lark-apaas/miaoda-data-cli db schema
193
+
194
+ # 查看 note 表 DDL
195
+ npx -y @lark-apaas/miaoda-data-cli db schema note
196
+
197
+ # 查看 note 表结构 JSON
198
+ npx -y @lark-apaas/miaoda-data-cli db schema note --json
199
+ ```
200
+
201
+ ### 默认输出(DDL)
202
+
203
+ ```sql
204
+ CREATE TABLE workspace_xxx.note (
205
+ id uuid NOT NULL DEFAULT gen_random_uuid(),
206
+ title character varying(255) NOT NULL,
207
+ content text NULL,
208
+ CONSTRAINT note_pkey PRIMARY KEY (id)
209
+ ) TABLESPACE pg_default;
210
+ COMMENT ON TABLE workspace_xxx.note IS 'note table';
211
+ ```
212
+
213
+ 多表时空行分隔,不存在的表输出 `-- table "xxx" not found`。
214
+
215
+ ### --json 输出
216
+
217
+ ```json
218
+ {
219
+ "tableName": "note",
220
+ "comment": "note table",
221
+ "type": "normal",
222
+ "fields": [
223
+ {
224
+ "fieldName": "id",
225
+ "type": "uuid",
226
+ "isNullable": false,
227
+ "defaultValue": "gen_random_uuid()",
228
+ "isPrimary": true,
229
+ "isUnique": false,
230
+ "isArray": false,
231
+ "isEnum": false
232
+ },
233
+ {
234
+ "fieldName": "title",
235
+ "type": "varchar",
236
+ "isNullable": false,
237
+ "defaultValue": null,
238
+ "isPrimary": false
239
+ }
240
+ ],
241
+ "indexes": [],
242
+ "relationships": []
243
+ }
244
+ ```