aitable-workflow-cli 0.1.20 → 0.1.21-beta.1787884824605
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/cli.js +41 -38
- package/package.json +3 -3
- package/templates/support-qa/lib/__tests__/dingtalk-group-membership.test.ts +17 -0
- package/templates/support-qa/lib/dingtalk-group-membership.ts +9 -2
- package/templates/support-qa/profiles/aitable/profile/profile.yml +2 -0
- package/templates/support-qa/profiles/default/profile/profile.yml +2 -0
- package/templates/support-qa/workflows/group-qa/recipes/on-action.recipe.ts +38 -9
- package/templates/support-qa/workflows/group-qa/recipes/reply-to-group.recipe.ts +27 -11
- package/templates/support-qa/workspaces/support/AGENTS.md +14 -6
package/dist/cli.js
CHANGED
|
@@ -1,33 +1,33 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{a as
|
|
3
|
-
\u26A0 \u53EF\u89C6\u5316\u754C\u9762\u5F02\u5E38\uFF0C\u56DE\u9000\u5230\u547D\u4EE4\u884C\u4EA4\u4E92: ${
|
|
2
|
+
import{a as We,b as Ge,l as He}from"./chunk-H5ZBCT4K.js";import"./chunk-XGHRD4FJ.js";import{b as X,d as N,e as ve,f as qe,g as Q}from"./chunk-N2ATMPTO.js";import{a as c}from"./chunk-EI7MMDWY.js";import{config as rn}from"dotenv";import{Command as Fr}from"commander";import{logger as on,activateGlobalDwsOnPath as Br,initSlsReport as Ur}from"aitable-workflow-core";import{createRequire as ln}from"module";var Ke="aitable-workflow-",pn=/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/;function Ve(){try{return ln(import.meta.url)("../package.json")}catch{return{}}}c(Ve,"loadCliPackageManifest");function ke(r){return r.version??"0.0.0"}c(ke,"resolveCliVersion");function Ye(r){let e=ke(r),t={};r.name?.startsWith(Ke)&&(t[r.name]=e);for(let[n,o]of Object.entries(r.dependencies??{}))n.startsWith(Ke)&&(t[n]=pn.test(o)?o:e);return t}c(Ye,"resolveMonorepoPackageVersions");import sn from"path";import{fileURLToPath as Nr}from"url";var Je="./aitable-workflow.config.yml";import{runInit as mn,logger as dn}from"aitable-workflow-core";import gn from"path";function ze(r,e){r.command("init").description("\u4EA4\u4E92\u5F0F\u521D\u59CB\u5316 \u2014 \u9009\u62E9 Coding Agent\u3001\u914D\u7F6E\u5DE5\u4F5C\u76EE\u5F55\u3001\u63CF\u8FF0\u4E1A\u52A1\u573A\u666F\uFF1B\u6216\u7528 --template \u4ECE\u5185\u7F6E\u6A21\u677F\u811A\u624B\u67B6").option("-c, --config <path>","\u76EE\u6807\u76EE\u5F55\u6216\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84\uFF08\u9ED8\u8BA4\u5F53\u524D\u76EE\u5F55\uFF09").option("-f, --force","\u8986\u76D6\u5DF2\u6709\u914D\u7F6E\u6587\u4EF6").option("-y, --yes","\u4F7F\u7528\u9ED8\u8BA4\u503C\u8DF3\u8FC7\u4EA4\u4E92\u5F0F\u5411\u5BFC").option("-t, --template <name>","\u4ECE\u5185\u7F6E\u6A21\u677F\u811A\u624B\u67B6(\u5F53\u524D\u63A8\u8350 support-qa),\u62F7\u8D1D\u5B8C\u6574\u53EF\u8FD0\u884C\u9879\u76EE\u5230\u76EE\u6807\u76EE\u5F55").option("--profile <name>","\u9009\u5B9A\u6A21\u677F\u4E0B\u7684 profile \u4E1A\u52A1\u5305(\u5982 support-qa \u7684 aitable),\u8986\u76D6\u5F0F\u53E0\u52A0\u5230\u5B9E\u4F8B").action(async t=>{try{await mn({config:t.config,force:!!t.force,yes:!!t.yes,template:t.template,profile:t.profile,templatesDir:gn.resolve(e.cliDir,"..","templates"),templatePackageVersions:e.packageVersions,cliVersion:e.version})}catch(n){n instanceof Error&&n.message.includes("cancelled")&&process.exit(0),dn.error("cli","\u521D\u59CB\u5316\u5931\u8D25",{error:n instanceof Error?n.message:String(n)}),process.exit(1)}})}c(ze,"registerInitCommand");import{runMigrate as un,logger as fn}from"aitable-workflow-core";import hn from"path";function Xe(r,e){r.command("migrate").description("\u5347\u7EA7\u9879\u76EE\u76EE\u5F55\u5230\u6700\u65B0\u6A21\u677F\u7248\u672C\uFF0C\u81EA\u52A8\u4FDD\u7559\u7528\u6237\u914D\u7F6E\uFF1B\u6216\u7528 --from/--to \u5728\u4E24\u4E2A\u76EE\u5F55\u95F4\u505A\u5408\u5E76\u8FC1\u79FB").option("-c, --config <path>","\u9879\u76EE\u76EE\u5F55\u8DEF\u5F84",e.defaultConfig).option("--from <dir>","\u6E90\u76EE\u5F55\uFF1A\u4EE5\u8BE5\u76EE\u5F55\u4E3A\u8FC1\u79FB\u57FA\u51C6\uFF08\u4E0D\u6309\u540D scaffold \u6A21\u677F\uFF09\uFF0C\u4E0E --template/--profile \u4E92\u65A5").option("--to <dir>","\u76EE\u6807\u9879\u76EE\u76EE\u5F55\uFF08--config \u7684\u540C\u4E49\u53C2\u6570\uFF0C\u4F18\u5148\u751F\u6548\uFF09").option("--dry-run","\u4EC5\u9884\u89C8\u53D8\u66F4\uFF0C\u4E0D\u5B9E\u9645\u5199\u5165").option("--no-backup","\u8DF3\u8FC7\u81EA\u52A8\u5907\u4EFD").option("--force","\u8DF3\u8FC7\u786E\u8BA4\u63D0\u793A").option("--template <name>","\u6307\u5B9A\u76EE\u6807\u6A21\u677F").option("--profile <name>","\u6307\u5B9A\u76EE\u6807 profile").option("--rollback","\u56DE\u6EDA\u5230\u4E0A\u4E00\u6B21\u8FC1\u79FB\u524D\u7684\u72B6\u6001").option("--list","\u5217\u51FA\u6240\u6709\u5907\u4EFD").action(async t=>{try{await un({config:t.config,from:t.from,to:t.to,dryRun:t.dryRun,noBackup:t.backup===!1,force:t.force,template:t.template,profile:t.profile,rollback:t.rollback,list:t.list,templatesDir:hn.resolve(e.cliDir,"..","templates"),templatePackageVersions:e.packageVersions,cliVersion:e.version})}catch(n){n instanceof Error&&n.message.includes("cancelled")&&process.exit(0),fn.error("cli","migrate \u5931\u8D25",{error:n instanceof Error?n.message:String(n)}),process.exit(1)}})}c(Xe,"registerMigrateCommand");import{runDesignCommand as wn,logger as yn}from"aitable-workflow-core";function Qe(r,e){r.command("design").description("\u8BA9 Coding Agent \u6839\u636E\u573A\u666F\u63CF\u8FF0\u751F\u6210\u5B8C\u6574\u7684 workflow \u548C recipes").option("-c, --config <path>","\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84",e.defaultConfig).option("-y, --yes","\u8DF3\u8FC7\u4EA4\u4E92\u5F0F\u8865\u5145\uFF0C\u76F4\u63A5\u4F7F\u7528 config \u4E2D\u7684 scene").option("--skip-questions","\u4E0D\u56DE\u7B54 Agent \u7684\u6F84\u6E05\u63D0\u95EE\uFF08Agent \u6309\u5047\u8BBE\u751F\u6210\u5E76\u5217\u51FA\u5F85\u786E\u8BA4\u9879\uFF09").action(async t=>{try{await wn(t)}catch(n){n instanceof Error&&n.message.includes("cancelled")&&process.exit(0),yn.error("cli","design \u5931\u8D25",{error:n instanceof Error?n.message:String(n)}),process.exit(1)}})}c(Qe,"registerDesignCommand");import{runCheck as vn,runSetupCommand as kn,checkMigrateHint as bn,logger as Cn,reportCliEvent as Z,setReportProject as Rn}from"aitable-workflow-core";import et from"path";function be(r){return r.skipConfig||r.skipConfigIfValid&&r.configValid===!0}c(be,"canSkipSetupConfig");function Ze(r){return be(r)?"setup":r.uiEnabled&&r.webAvailable?"web":"cli"}c(Ze,"resolveSetupConfigRoute");function tt(r,e){r.command("setup").description("\u6821\u9A8C\u5E76\u8865\u9F50\u9879\u76EE\u914D\u7F6E\uFF0C\u518D\u521D\u59CB\u5316 AI \u8868\u683C\u7ED3\u6784 + \u51C6\u5907\u77E5\u8BC6\u5E93 \u2014 \u4E00\u6B21\u5B8C\u6210 config + \u5EFA\u8868").option("-c, --config <path>","\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84",e.defaultConfig).option("--no-clean","\u4E0D\u6E05\u7406\u7A7A\u767D\u884C").option("--no-kb","\u8DF3\u8FC7\u77E5\u8BC6\u5E93\u51C6\u5907/\u7F16\u8BD1").option("--kb-full","\u5F3A\u5236\u5168\u91CF\u91CD\u7F16\u8BD1\u77E5\u8BC6\u5E93").option("--skip-config","\u8DF3\u8FC7\u524D\u7F6E\u7684\u4EA4\u4E92\u5F0F\u914D\u7F6E\u6821\u9A8C\uFF08CI/\u5DF2\u786E\u8BA4\u914D\u7F6E\u65F6\u4F7F\u7528\uFF09").option("--skip-config-if-valid","\u5148\u6821\u9A8C\u914D\u7F6E\uFF1B\u65E0\u963B\u65AD\u9879\u65F6\u8DF3\u8FC7\u914D\u7F6E\u754C\u9762\u5E76\u76F4\u63A5\u5F00\u59CB\u642D\u5EFA").option("--no-ui","\u5F3A\u5236\u4F7F\u7528\u547D\u4EE4\u884C\u4EA4\u4E92\uFF08\u8DF3\u8FC7\u53EF\u89C6\u5316\u914D\u7F6E\u754C\u9762\uFF09").action(async t=>{Rn(t.config);let n=Date.now();Z("setup.start",{skipConfig:t.skipConfig===!0,clean:t.clean,kb:t.kb,kbFull:t.kbFull===!0,ui:t.ui!==!1});try{if(await bn(et.dirname(et.resolve(t.config)),e.version),!t.skipConfig){let o=t.ui!==!1,i=!1;if(t.skipConfigIfValid){console.log("\u{1F4CB} \u6B65\u9AA4 1/2\uFF1A\u6821\u9A8C\u9879\u76EE\u914D\u7F6E");let s=await vn({configPath:t.config});if(i=be({skipConfig:!1,skipConfigIfValid:!0,configValid:s.ok}),i){let a=[];s.summary.warnings>0&&a.push(`${s.summary.warnings} \u9879\u8B66\u544A`),s.summary.suggestions>0&&a.push(`${s.summary.suggestions} \u9879\u5EFA\u8BAE`);let l=a.length>0?`\uFF08${a.join("\uFF0C")}\uFF0C\u5747\u4E0D\u963B\u65AD\uFF09`:"";console.log(`\u2713 \u914D\u7F6E\u65E0\u963B\u65AD\u9879${l}\uFF0C\u8DF3\u8FC7\u914D\u7F6E\u754C\u9762\u5E76\u7EE7\u7EED\u642D\u5EFA\u3002`)}else console.log(`\u2139 \u68C0\u6D4B\u5230 ${s.summary.errors} \u9879\u963B\u65AD\uFF0C\u8FDB\u5165\u914D\u7F6E\u73AF\u8282\u3002`)}if(!i&&o){let{hasWebRoot:s,runConfigUI:a}=await import("./config-ui-server-B7QFGMVV.js");if(Ze({skipConfig:!1,skipConfigIfValid:t.skipConfigIfValid===!0,uiEnabled:o,webAvailable:s(),configValid:i})==="web"){console.log("\u{1F4CB} \u6B65\u9AA4 1/2\uFF1A\u53EF\u89C6\u5316\u914D\u7F6E\u754C\u9762");try{await a({configPath:t.config,setupOptions:{clean:t.clean,kb:t.kb,kbFull:t.kbFull}}),Z("setup.finish",{success:!0,route:"web",durationMs:Date.now()-n});return}catch(p){console.warn(`
|
|
3
|
+
\u26A0 \u53EF\u89C6\u5316\u754C\u9762\u5F02\u5E38\uFF0C\u56DE\u9000\u5230\u547D\u4EE4\u884C\u4EA4\u4E92: ${p instanceof Error?p.message:String(p)}
|
|
4
4
|
`)}}}if(!i){console.log("\u{1F4CB} \u6B65\u9AA4 1/2\uFF1A\u6821\u9A8C\u5E76\u8865\u9F50\u9879\u76EE\u914D\u7F6E");let{runConfigCommand:s}=await import("./config-command-HL4O3DHK.js");(await s({configPath:t.config,interactive:!0,offerSkipToBuild:!0})).ok||(console.error(`
|
|
5
5
|
\u2717 \u9879\u76EE\u914D\u7F6E\u4ECD\u4E0D\u5B8C\u6574\uFF0C\u5DF2\u4E2D\u6B62 setup\u3002\u8BF7\u8865\u9F50\u5FC5\u586B\u9879\u540E\u91CD\u8BD5\uFF08\u6216\u7528 --skip-config \u8DF3\u8FC7\uFF09\u3002`),process.exit(1))}console.log(`
|
|
6
6
|
\u{1F527} \u6B65\u9AA4 2/2\uFF1A\u521D\u59CB\u5316\u8868\u7ED3\u6784 + \u51C6\u5907\u77E5\u8BC6\u5E93
|
|
7
|
-
`)}await
|
|
8
|
-
`));try{await Dn();let o=await(await
|
|
7
|
+
`)}await kn(t),await Z("setup.finish",{success:!0,durationMs:Date.now()-n})}catch(o){Cn.error("cli","setup \u5931\u8D25",{error:o instanceof Error?o.message:String(o)}),await Z("setup.finish",{success:!1,error:(o instanceof Error?o.message:String(o)).slice(0,500),durationMs:Date.now()-n}),process.exit(1)}})}c(tt,"registerSetupCommand");import nt from"path";import{createApp as ot,ensureTsxRegistered as In,checkMigrateHint as An,logger as R,runCheck as Sn}from"aitable-workflow-core";function En(){let t=[],n=c(()=>{let o=Date.now();for(t.push(o);t.length>0&&o-t[0]>6e4;)t.shift();t.length>=10&&(R.error("cli",`60000ms \u5185\u53D1\u751F ${t.length} \u6B21\u672A\u6355\u83B7\u5F02\u5E38\uFF0C\u7591\u4F3C\u975E\u5076\u53D1\u9519\u8BEF\uFF0C\u4E3B\u52A8\u9000\u51FA`),process.exit(1))},"recordCrashAndMaybeExit");process.on("uncaughtException",o=>{R.error("cli","\u6355\u83B7\u5230\u672A\u5904\u7406\u5F02\u5E38\uFF08\u5DF2\u5B58\u6D3B\uFF0C\u4EA4\u7531\u91CD\u8FDE/\u91CD\u8BD5\u903B\u8F91\u81EA\u6108\uFF09",{error:o instanceof Error?o.message:String(o),stack:o instanceof Error?o.stack:void 0}),n()}),process.on("unhandledRejection",o=>{R.error("cli","\u6355\u83B7\u5230\u672A\u5904\u7406 Promise \u62D2\u7EDD\uFF08\u5DF2\u5B58\u6D3B\uFF09",{error:o instanceof Error?o.message:String(o),stack:o instanceof Error?o.stack:void 0}),n()})}c(En,"installLongRunningCrashGuards");function rt(r,e){r.command("start").description("\u542F\u52A8\u7EDF\u4E00\u63A7\u5236\u53F0\uFF1A\u914D\u7F6E\u68C0\u6D4B + \u642D\u5EFA + \u8FD0\u884C\u4E00\u4F53\u5316").option("-c, --config <path>","\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84",e.defaultConfig).option("--no-open","\u542F\u52A8\u540E\u4E0D\u8981\u81EA\u52A8\u6253\u5F00\u6D4F\u89C8\u5668").option("--skip-config-if-valid","\u914D\u7F6E\u5DF2\u5B8C\u6574\u65F6\u76F4\u63A5\u542F\u52A8\u8FD0\u884C").action(async t=>{try{await An(nt.dirname(nt.resolve(t.config)),e.version),En(),await In();let n=await Sn({configPath:t.config}),o=!n.ok||n.summary.errors>0,i,s="setup";if(!o)try{s="runtime",i=await ot(t.config,{})}catch(p){R.warn("cli","\u914D\u7F6E\u6821\u9A8C\u901A\u8FC7\u4F46\u88C5\u914D\u5931\u8D25\uFF0C\u56DE\u9000\u5230\u642D\u5EFA\u6A21\u5F0F",{error:p instanceof Error?p.message:String(p)}),s="setup",i=void 0}s==="runtime"&&i?t.skipConfigIfValid?(R.info("cli","\u914D\u7F6E\u5DF2\u5B8C\u6574\uFF0C\u76F4\u63A5\u542F\u52A8\u8FD0\u884C"),i.start().catch(p=>{R.error("cli","\u8FD0\u884C\u542F\u52A8\u5931\u8D25\uFF0C\u53EF\u5728\u63A7\u5236\u53F0\u91CD\u8BD5\u542F\u52A8",{error:p instanceof Error?p.message:String(p)})})):R.info("cli","\u914D\u7F6E\u5DF2\u5B8C\u6574\uFF0C\u8BF7\u5728\u63A7\u5236\u53F0\u70B9\u51FB\u300C\u5F00\u59CB\u8FD0\u884C\u300D\u542F\u52A8\u670D\u52A1"):R.info("cli","\u914D\u7F6E\u4E0D\u5B8C\u6574\uFF0C\u63A7\u5236\u53F0\u5C06\u5F15\u5BFC\u5B8C\u6210\u642D\u5EFA");let a=!1,l=c(async()=>{a&&(R.warn("cli","\u518D\u6B21\u6536\u5230\u9000\u51FA\u4FE1\u53F7\uFF0C\u5F3A\u5236\u9000\u51FA"),process.exit(1)),a=!0,R.info("cli","\u6536\u5230\u9000\u51FA\u4FE1\u53F7\uFF0C\u6B63\u5728\u505C\u6B62\u8FD0\u884C...");try{await i?.stop()}finally{process.exit(0)}},"shutdown");process.on("SIGINT",l),process.on("SIGTERM",l),await He({configPath:t.config,mode:s,app:i,open:t.open,exitOnSetupError:!1,...s==="runtime"&&t.skipConfigIfValid?{initialRunStatus:{status:"running",since:new Date().toISOString(),lastError:null}}:{},...s==="setup"?{assembleApp:c(p=>ot(p,{}),"assembleApp"),onAppAssembled:c(p=>{i=p,R.info("cli","\u63A7\u5236\u53F0\u5DF2\u5B8C\u6210 setup\u2192runtime \u664B\u5347\uFF0C\u53EF\u5728\u63A7\u5236\u53F0\u542F\u52A8\u8FD0\u884C")},"onAppAssembled")}:{}})}catch(n){R.error("cli","start \u547D\u4EE4\u5931\u8D25",{error:n instanceof Error?n.message:String(n)}),process.exit(1)}})}c(rt,"registerStartCommand");import{createApp as xn,ensureTsxRegistered as Tn,checkMigrateHint as $n,logger as O,reportCliEvent as ee,setReportProject as Pn}from"aitable-workflow-core";import it from"path";function jn(){let t=[],n=c(async()=>{let o=Date.now();for(t.push(o);t.length>0&&o-t[0]>6e4;)t.shift();t.length>=10&&(O.error("cli",`60000ms \u5185\u53D1\u751F ${t.length} \u6B21\u672A\u6355\u83B7\u5F02\u5E38\uFF0C\u7591\u4F3C\u975E\u5076\u53D1\u9519\u8BEF\uFF0C\u4E3B\u52A8\u9000\u51FA`),await ee("run.exit",{reason:"crash-loop",crashes:t.length}),process.exit(1))},"recordCrashAndMaybeExit");process.on("uncaughtException",o=>{O.error("cli","\u6355\u83B7\u5230\u672A\u5904\u7406\u5F02\u5E38\uFF08\u5DF2\u5B58\u6D3B\uFF0C\u4EA4\u7531\u91CD\u8FDE/\u91CD\u8BD5\u903B\u8F91\u81EA\u6108\uFF09",{error:o instanceof Error?o.message:String(o),stack:o instanceof Error?o.stack:void 0}),n()}),process.on("unhandledRejection",o=>{O.error("cli","\u6355\u83B7\u5230\u672A\u5904\u7406 Promise \u62D2\u7EDD\uFF08\u5DF2\u5B58\u6D3B\uFF09",{error:o instanceof Error?o.message:String(o),stack:o instanceof Error?o.stack:void 0}),n()})}c(jn,"installLongRunningCrashGuards");function st(r,e){r.command("run").description("\u542F\u52A8\u7F16\u6392\u5F15\u64CE\uFF0C\u5F00\u59CB\u8F6E\u8BE2\u5904\u7406\u5DE5\u5355").option("-c, --config <path>","\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84",e.defaultConfig).option("--emit-events <channel>","\u8FD0\u884C\u4E8B\u4EF6\u4E0A\u629B\u901A\u9053\uFF1Aipc = \u7ECF process.send \u53D1\u7ED9\u7236\u8FDB\u7A0B\uFF08daemon runner \u7528\uFF09\uFF0C\u7F3A\u7701\u4E0D\u4E0A\u629B").action(async t=>{Pn(t.config);try{await $n(it.dirname(it.resolve(t.config)),e.version),jn(),await Tn();let n=t.emitEvents==="ipc"&&typeof process.send=="function",o=await xn(t.config,n?{onRunEvent:c(a=>{process.send?.({type:"run-event",event:a})},"onRunEvent")}:{});ee("run.start",{emitEvents:t.emitEvents??null});let i=!1,s=c(async()=>{i&&(O.warn("cli","\u518D\u6B21\u6536\u5230\u9000\u51FA\u4FE1\u53F7\uFF0C\u5F3A\u5236\u9000\u51FA"),process.exit(1)),i=!0,O.info("cli","\u6536\u5230\u9000\u51FA\u4FE1\u53F7\uFF0C\u6B63\u5728\u7B49\u5F85\u8FDB\u884C\u4E2D\u7684\u4EFB\u52A1\u5B8C\u6210...");try{await o.stop()}finally{await ee("run.exit",{reason:"signal"}),process.exit(0)}},"shutdown");process.on("SIGINT",s),process.on("SIGTERM",s),await o.start()}catch(n){O.error("cli","\u542F\u52A8\u5931\u8D25",{error:n instanceof Error?n.message:String(n)}),await ee("run.exit",{reason:"start-failure",error:(n instanceof Error?n.message:String(n)).slice(0,500)}),process.exit(1)}})}c(st,"registerRunCommand");import{createApp as On,ensureTsxRegistered as Dn,logger as Ce}from"aitable-workflow-core";function _n(r,e){let t=`\u5DE5\u4F5C\u6D41 ${r} / \u4E8B\u4EF6\u6E90 ${e.sourceName}\uFF08handler: ${e.handlerRecipe}\uFF09`;return e.actions===0?`${t}\uFF1A\u5DF2\u89E6\u53D1\u4F46\u6CA1\u6709\u4EA7\u51FA\u52A8\u4F5C\uFF08\u8017\u65F6 ${e.durationMs}ms\uFF09\u3002\u8BF7\u68C0\u67E5\u6570\u636E\u6E90\u662F\u5426\u6709\u53EF\u7528\u884C\u3002`:`${t}\uFF1A\u4EA7\u51FA ${e.actions} \u4E2A\u52A8\u4F5C\uFF08\u843D\u5E93 ${e.applied}\uFF0C\u77AC\u6001 ${e.transient}\uFF09\uFF0C\u8017\u65F6 ${e.durationMs}ms\u3002`}c(_n,"formatTriggerOnceResult");function at(r){return new Promise(e=>{r.write("",()=>e())})}c(at,"flushStream");function ct(){return Promise.all([at(process.stdout),at(process.stderr)])}c(ct,"flushOutputs");function lt(r,e){r.command("trigger-once").description("\u5355\u6B21\u89E6\u53D1\u67D0\u4E2A\u5DE5\u4F5C\u6D41\u7684\u4E8B\u4EF6\u6E90 handler \u8DD1\u4E00\u904D\uFF08\u4E0D\u542F\u52A8\u5E38\u9A7B\u5F15\u64CE\uFF09").requiredOption("-w, --workflow <id>","\u5DE5\u4F5C\u6D41 ID\uFF08\u591A\u5DE5\u4F5C\u6D41\u9879\u76EE\u91CC\u7684\u76EE\u5F55\u540D\uFF09").option("-c, --config <path>","\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84",e.defaultConfig).option("-s, --source <name>","\u53EA\u89E6\u53D1\u6307\u5B9A\u4E8B\u4EF6\u6E90\uFF1B\u7F3A\u7701\u7528\u8BE5\u5DE5\u4F5C\u6D41\u7684\u7B2C\u4E00\u4E2A\u4E8B\u4EF6\u6E90").option("--json","\u4EE5 JSON \u8F93\u51FA\u7ED3\u679C\uFF08\u4F9B\u811A\u672C\u6D88\u8D39\uFF09").action(async t=>{t.json&&Ce.setSink((n,o)=>process.stderr.write(`${o}
|
|
8
|
+
`));try{await Dn();let o=await(await On(t.config)).triggerWorkflowOnce(t.workflow,{...t.source?{sourceName:t.source}:{}});t.json?process.stdout.write(`${JSON.stringify({ok:!0,workflowId:t.workflow,...o})}
|
|
9
9
|
`):Ce.info("cli",_n(t.workflow,o)),await ct(),process.exit(0)}catch(n){let o=n instanceof Error?n.message:String(n);t.json?process.stdout.write(`${JSON.stringify({ok:!1,error:o})}
|
|
10
|
-
`):Ce.error("cli","\u89E6\u53D1\u5931\u8D25",{error:o}),await ct(),process.exit(1)}})}c(lt,"registerTriggerCommand");import{loadSettings as
|
|
11
|
-
\u6B65\u9AA4\u94FE:`);for(let[i,s]of o.orderedStepIds.entries()){let a=o.steps.find(
|
|
12
|
-
\u914D\u7F6E\u6458\u8981:`),console.log(` AI \u8868\u683C: base=${n.tracker.base_id} table=${n.tracker.table_id}`),console.log(` \u5F15\u64CE: ${n.agent.engine}`),console.log(` \u8F6E\u8BE2\u95F4\u9694: ${n.polling.interval_ms}ms`)}catch(n){console.error(`\u2717 \u9A8C\u8BC1\u5931\u8D25: ${n instanceof Error?n.message:String(n)}`),process.exit(1)}})}c(
|
|
10
|
+
`):Ce.error("cli","\u89E6\u53D1\u5931\u8D25",{error:o}),await ct(),process.exit(1)}})}c(lt,"registerTriggerCommand");import{loadSettings as Ln,loadWorkflowTemplate as Mn}from"aitable-workflow-core";function pt(r,e){r.command("validate").description("\u9A8C\u8BC1\u914D\u7F6E\u6587\u4EF6\u548C\u5DE5\u4F5C\u6D41\u6A21\u677F").option("-c, --config <path>","\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84",e.defaultConfig).action(async t=>{try{let n=await Ln(t.config);console.log("\u2713 \u914D\u7F6E\u6587\u4EF6\u9A8C\u8BC1\u901A\u8FC7");let o=await Mn(n.workflowTemplatePath,{envSource:n.envSource});console.log(`\u2713 \u5DE5\u4F5C\u6D41\u6A21\u677F\u9A8C\u8BC1\u901A\u8FC7: ${o.meta.name} (${o.orderedStepIds.length} \u6B65)`),console.log(`
|
|
11
|
+
\u6B65\u9AA4\u94FE:`);for(let[i,s]of o.orderedStepIds.entries()){let a=o.steps.find(p=>p.id===s),l=i<o.orderedStepIds.length-1?" \u2192":" \u2713";console.log(` ${i+1}. [${s}] ${a?.name??"?"}${l}`)}console.log(`
|
|
12
|
+
\u914D\u7F6E\u6458\u8981:`),console.log(` AI \u8868\u683C: base=${n.tracker.base_id} table=${n.tracker.table_id}`),console.log(` \u5F15\u64CE: ${n.agent.engine}`),console.log(` \u8F6E\u8BE2\u95F4\u9694: ${n.polling.interval_ms}ms`)}catch(n){console.error(`\u2717 \u9A8C\u8BC1\u5931\u8D25: ${n instanceof Error?n.message:String(n)}`),process.exit(1)}})}c(pt,"registerValidateCommand");import{logger as Fn}from"aitable-workflow-core";function Bn(r,e){if(r.getOptionValueSource("config")!=="default")return e;let t=r.parent?.getOptionValue("config");return typeof t=="string"?t:e}c(Bn,"effectiveConfigPath");function mt(r,e){r.command("config").description("\u6821\u9A8C\u9879\u76EE\u914D\u7F6E\u5B8C\u6574\u6027\uFF0C\u5E76\u53EF\u4EA4\u4E92\u5F0F\u4FEE\u6539\u53C2\u6570").option("-c, --config <path>","\u9879\u76EE\u76EE\u5F55\u6216\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84",e.defaultConfig).option("-w, --workflow <id>","\u591A\u5DE5\u4F5C\u6D41\u6A21\u5F0F\uFF1A\u6307\u5B9A\u6821\u9A8C\u7684\u5DE5\u4F5C\u6D41 ID\uFF08\u7F3A\u7701\u6821\u9A8C\u5168\u90E8\uFF09").option("--json","JSON \u683C\u5F0F\u8F93\u51FA\u6821\u9A8C\u7ED3\u679C").option("--no-interactive","\u4EC5\u6821\u9A8C\uFF0C\u8DF3\u8FC7\u4EA4\u4E92\u5F0F\u4FEE\u6539").action(async n=>{try{let{runConfigCommand:o}=await import("./config-command-HL4O3DHK.js");await o({configPath:n.config,workflowId:n.workflow,json:n.json,interactive:n.interactive})}catch(o){o instanceof Error&&o.message.includes("cancelled")&&process.exit(0),Fn.error("cli","config \u547D\u4EE4\u5931\u8D25",{error:o instanceof Error?o.message:String(o)}),process.exit(1)}}).command("set").description("\u975E\u4EA4\u4E92\u5199\u914D\u7F6E\u9879\uFF08key \u53D6\u81EA config --json \u7684 item.key\uFF09").option("-c, --config <path>","\u9879\u76EE\u76EE\u5F55\u6216\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84",e.defaultConfig).option("-w, --workflow <id>","\u591A\u5DE5\u4F5C\u6D41\u9879\u76EE\u91CC\u7ED9 key \u6D88\u6B67\uFF08\u540C\u540D key \u547D\u4E2D\u591A\u4E2A\u5DE5\u4F5C\u6D41\u65F6\u5FC5\u586B\uFF09").option("--set <key=value>","\u5199\u4E00\u9879\uFF1B\u53EF\u91CD\u590D\u3002\u654F\u611F\u9879\u62D2\u6536\uFF08\u503C\u4F1A\u8FDB argv\uFF09\uFF0C\u6539\u7528 --set-stdin",(n,o)=>[...o,n],[]).option("--set-stdin <key>","\u5199\u4E00\u9879\uFF0C\u503C\u4ECE stdin \u9010\u884C\u6309\u987A\u5E8F\u8BFB\uFF1B\u53EF\u91CD\u590D\u3002\u654F\u611F\u9879\u8D70\u8FD9\u6761",(n,o)=>[...o,n],[]).option("--dry-run","\u53EA\u6253\u5370\u5C06\u5199\u54EA\u4E2A\u6587\u4EF6\u7684\u54EA\u4E2A\u8DEF\u5F84\uFF0C\u4E0D\u5199\u76D8\uFF08\u654F\u611F\u503C\u53EA\u6253\u952E\u540D\uFF09").option("--json","JSON \u683C\u5F0F\u8F93\u51FA\u7ED3\u679C").action(async(n,o)=>{try{let{runConfigSet:i,printConfigSetResult:s}=await import("./config-set-EGLLIMVJ.js"),a=await i({configPath:Bn(o,n.config),...n.workflow?{workflowId:n.workflow}:{},set:n.set,setStdin:n.setStdin,...n.dryRun?{dryRun:!0}:{},...n.json?{json:!0}:{}});s(a,n.json===!0)}catch(i){let s=i instanceof Error?i.message:String(i);n.json?process.stdout.write(JSON.stringify({ok:!1,error:s},null,2)+`
|
|
13
13
|
`):console.error(`
|
|
14
14
|
\u2717 ${s}
|
|
15
|
-
`),process.exit(1)}})}c(
|
|
15
|
+
`),process.exit(1)}})}c(mt,"registerConfigCommand");import{runSync as Un,logger as Nn}from"aitable-workflow-core";function dt(r,e){r.command("sync").description("\u68C0\u6D4B\u914D\u7F6E\u4E0E\u8868\u7684\u504F\u5DEE\uFF08\u5B57\u6BB5\u7C7B\u578B/\u9009\u9879 drift\uFF09\uFF0C\u8F93\u51FA\u8BCA\u65AD\u62A5\u544A\u548C\u4FEE\u590D\u5EFA\u8BAE").option("-c, --config <path>","\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84",e.defaultConfig).option("-y, --yes","\u975E\u4EA4\u4E92\u6A21\u5F0F").action(async t=>{try{await Un({configPath:t.config,yes:t.yes})}catch(n){Nn.error("cli","sync \u5931\u8D25",{error:n instanceof Error?n.message:String(n)}),process.exit(1)}})}c(dt,"registerSyncCommand");import{listCodingAgents as Wn,logger as Gn}from"aitable-workflow-core";function gt(r,e){r.command("agents").description("\u5217\u51FA\u5168\u90E8\u652F\u6301\u7684 Coding Agent \u9002\u914D\u5668\uFF0C\u5E76\u6807\u6CE8\u672C\u673A\u5DF2\u63A2\u6D4B\u5230\u5B89\u88C5\u7684").option("--json","\u4EE5 JSON \u683C\u5F0F\u8F93\u51FA").action(t=>{try{let n=Wn();if(t.json){process.stdout.write(JSON.stringify(n,null,2)+`
|
|
16
16
|
`);return}let o=n.filter(i=>i.installed).length;console.log(`
|
|
17
17
|
\u652F\u6301 ${n.length} \u4E2A Coding Agent\uFF0C\u672C\u673A\u5DF2\u63A2\u6D4B\u5230 ${o} \u4E2A\uFF1A
|
|
18
|
-
`);for(let i of n){let s=i.installed?"\u2713":"\u2717",a=[i.bin,...i.fallbackBins].join(" / "),l=i.installed?`\u5DF2\u5B89\u88C5 (${i.detectedCmd}${i.detectedVersion?` ${i.detectedVersion}`:""})`:"\u672A\u63A2\u6D4B\u5230";console.log(` ${s} ${i.name.padEnd(16)} [${i.id}] bin: ${a.padEnd(24)} ${l}`)}console.log("")}catch(n){Nn.error("cli","\u5217\u51FA agents \u5931\u8D25",{error:n instanceof Error?n.message:String(n)}),process.exit(1)}})}c(gt,"registerAgentsCommand");import{logger as Gn}from"aitable-workflow-core";import{startServer as Hn,resolveServeConfig as qn}from"aitable-workflow-server";import te from"path";import{existsSync as ut}from"fs";function ft(r,e){r.command("serve").description("\u542F\u52A8\u672C\u5730 HTTP API \u670D\u52A1\u5E76\u6258\u7BA1 Web \u524D\u7AEF\u9875\u9762\uFF08local \u6A21\u5F0F\u81EA\u52A8\u6253\u5F00\u6D4F\u89C8\u5668\uFF09").option("-p, --port <number>","\u76D1\u542C\u7AEF\u53E3\uFF08\u9ED8\u8BA4 8787\uFF0C\u53EF\u7528 AITABLE_WORKFLOW_PORT \u8986\u76D6\uFF09").option("--host <host>","\u76D1\u542C\u5730\u5740\uFF08\u9ED8\u8BA4 127.0.0.1\uFF0C\u53EF\u7528 AITABLE_WORKFLOW_HOST \u8986\u76D6\uFF09").option("--data-root <path>","\u9879\u76EE\u5DE5\u4F5C\u533A\u6570\u636E\u76EE\u5F55\uFF08\u9ED8\u8BA4 .sandbox/aitable-workflow-server\uFF0C\u53EF\u7528 AITABLE_WORKFLOW_DATA_DIR \u8986\u76D6\uFF09").option("--web-root <path>","Web \u524D\u7AEF\u9759\u6001\u8D44\u6E90\u76EE\u5F55\uFF08\u9ED8\u8BA4\u4F7F\u7528\u968F CLI \u6253\u5305\u7684 dist/web\uFF09").option("--no-open","\u542F\u52A8\u540E\u4E0D\u81EA\u52A8\u6253\u5F00\u6D4F\u89C8\u5668\uFF08\u9ED8\u8BA4\u81EA\u52A8\u6253\u5F00\uFF09").action(async t=>{try{let n={};if(t.port!==void 0){let i=Number.parseInt(t.port,10);Number.isFinite(i)&&i>0&&(n.port=i)}if(t.host!==void 0&&(n.host=t.host),t.dataRoot!==void 0&&(n.dataRoot=t.dataRoot),t.webRoot!==void 0)n.webRoot=t.webRoot;else{let i=te.join(e.cliDir,"web");if(ut(te.join(i,"index.html")))n.webRoot=i;else{let s=te.resolve(e.cliDir,"..","..","web","dist");ut(te.join(s,"index.html"))&&(n.webRoot=s)}}let o=qn(n);if(await Hn(o),o.webRoot&&t.open!==!1){let i=o.host==="0.0.0.0"?"127.0.0.1":o.host;Ge(`http://${i}:${o.port}`)}}catch(n){Gn.error("cli","HTTP \u670D\u52A1\u542F\u52A8\u5931\u8D25",{error:n instanceof Error?n.message:String(n)}),process.exit(1)}})}c(ft,"registerServeCommand");import{Command as Oe}from"commander";import{logger as S,listCredentials as Bt,getActiveCredential as j,maskSecret as ir}from"aitable-workflow-core";import F from"os";import M from"path";import{mkdir as _o,readFile as Oo}from"fs/promises";import{mkdirSync as Lo}from"fs";import{logger as w,detectInstalledAgents as Mo,runCodingAgent as Fo,DwsClient as Bo,getActiveCredential as Uo,assertPortableId as Pt,resolveInsideRoot as $t,probeCommand as Wo}from"aitable-workflow-core";import Re from"ws";import{logger as E,RELAY_REJECTED_CODE as Kn,isRelayMessage as Vn}from"aitable-workflow-core";var T="relay:conn",ht=1e3,Yn=3e4,Jn=Number(process.env.AITABLE_RELAY_SERVER_STALE_MS??9e4),ne=class{static{c(this,"RelayConnection")}ws=null;delay=ht;timer=null;watchdog=null;should=!0;permanentlyRejected=!1;accepted=!1;lastServerFrameAt=0;serverUrl;key;token;onMessage;onOpen;onAccepted;onPermanentReject;mkWs;staleMs;constructor(e){this.serverUrl=e.serverUrl,this.key=e.key??"",this.token=e.token??"",this.onMessage=e.onMessage,this.onOpen=e.onOpen,this.onAccepted=e.onAccepted,this.onPermanentReject=e.onPermanentReject,this.mkWs=e.mkWs??(t=>new Re(t)),this.staleMs=e.staleMs??Jn}connect(){this.permanentlyRejected||(this.should=!0,this.doConnect())}send(e){this.ws&&this.ws.readyState===Re.OPEN&&this.ws.send(JSON.stringify(e))}close(){this.should=!1,this.timer&&(clearTimeout(this.timer),this.timer=null),this.watchdog&&(clearTimeout(this.watchdog),this.watchdog=null),this.ws?.close()}isPermanentlyRejected(){return this.permanentlyRejected}currentSocket(){return this.ws}buildUrl(){let e=this.serverUrl.replace(/\/+$/,"").replace(/^http/,"ws"),t=this.token?`token=${encodeURIComponent(this.token)}`:`key=${encodeURIComponent(this.key)}`;return`${e}/api/relay/connect?${t}`}doConnect(){if(!this.should||this.permanentlyRejected)return;let e=this.buildUrl();E.info(T,"connecting",{serverUrl:this.serverUrl}),this.watchdog&&(clearTimeout(this.watchdog),this.watchdog=null),this.accepted=!1,this.lastServerFrameAt=0;let t=this.mkWs(e);this.ws=t,t.on("open",()=>{E.info(T,"connected"),this.onOpen?.(t)}),t.on("message",n=>{this.accepted||(this.accepted=!0,this.delay=ht,this.onAccepted?.(t)),this.lastServerFrameAt=Date.now(),this.armWatchdog();let o;try{o=JSON.parse(typeof n=="string"?n:String(n))}catch{return}Vn(o)&&(o.type==="ping"&&this.send({type:"pong"}),this.onMessage(o))}),t.on("close",(n,o)=>{this.watchdog&&(clearTimeout(this.watchdog),this.watchdog=null);let i=o==null?"":String(o);if(n===Kn){this.permanentlyRejected=!0,this.should=!1,E.error(T,"server permanently rejected this machine: its connection key is unknown or was removed; re-issue the relay connect command and restart with the new key",{code:n,reason:i}),this.onPermanentReject?.({code:n,reason:i});return}E.warn(T,"disconnected",{code:n}),this.scheduleReconnect()}),t.on("error",n=>{E.error(T,"ws error",{detail:n instanceof Error?n.message:String(n)})})}armWatchdog(){this.watchdog&&clearTimeout(this.watchdog),this.watchdog=setTimeout(()=>{if(!(!this.accepted||!this.lastServerFrameAt||this.ws?.readyState!==Re.OPEN)){E.warn(T,"server heartbeat stale; closing socket to reconnect",{staleMs:Date.now()-this.lastServerFrameAt});try{this.ws.close()}catch{}}},this.staleMs+1),this.watchdog.unref?.()}scheduleReconnect(){!this.should||this.permanentlyRejected||this.timer||(E.info(T,"reconnecting",{ms:this.delay}),this.timer=setTimeout(()=>{this.timer=null,this.doConnect()},this.delay),this.timer.unref?.(),this.delay=Math.min(this.delay*2,Yn))}};import{createHash as zn}from"crypto";import{readdir as Xn,readFile as Qn,stat as Zn}from"fs/promises";import wt from"path";import{isAllowedArtifactPath as eo,normalizeArtifactRelPath as to}from"aitable-workflow-core";var yt={async upload(r,e,t){let n=await fetch(r,{method:"PUT",body:e,headers:{...t}});if(!n.ok)throw new Error(`artifact PUT failed: ${n.status} ${n.statusText}`)}};async function vt(r){let e=[];return await kt(r,r,e),e.sort((t,n)=>t.relPath.localeCompare(n.relPath))}c(vt,"scanArtifacts");async function kt(r,e,t){let n;try{n=await Xn(e,{withFileTypes:!0})}catch{return}for(let o of n){if(o.name==="node_modules"||o.name.startsWith("."))continue;let i=wt.join(e,o.name);if(o.isDirectory())await kt(r,i,t);else if(o.isFile()){let s=wt.relative(r,i).replace(/\\/g,"/");if(!eo(s))continue;let a=await Zn(i),l=await Qn(i),m=zn("sha256").update(l).digest("hex");t.push({relPath:to(s),absPath:i,size:a.size,sha256:m})}}}c(kt,"walk");import Tt from"path";import{logger as k}from"aitable-workflow-core";import{logger as _,spawnManagedProcess as no}from"aitable-workflow-core";var O="engine-supervisor",oe=class{static{c(this,"EngineSupervisor")}options;doSpawn;setTimer;clearTimer;child=null;childGen=0;consecutiveFailures=0;lastExitCode=null;lastError=null;restartTimer=null;stopped=!0;constructor(e){this.options=e,this.doSpawn=e.spawnImpl??no,this.setTimer=e.setTimeoutImpl??((t,n)=>setTimeout(t,n)),this.clearTimer=e.clearTimeoutImpl??(t=>clearTimeout(t))}get status(){return{running:this.child!=null,pid:this.child?.managed.child.pid??null,generation:this.childGen,startedAt:this.child?new Date(this.child.startedAt).toISOString():null,consecutiveFailures:this.consecutiveFailures,lastExitCode:this.lastExitCode,lastError:this.lastError}}start(){this.stopped=!1,!this.child&&this.spawnChild()}async stop(){this.stopped=!0,this.restartTimer&&(this.clearTimer(this.restartTimer),this.restartTimer=null);let e=this.child;if(!e)return;e.stopping=!0;let t=e.managed.child.pid;if(t==null){this.child=null;return}let n=e.gen;await e.managed.terminateTree({gracePeriodMs:this.options.drainTimeoutMs??2e4}),e.managed.child.exitCode==null&&e.managed.child.signalCode==null&&(_.error(O,"\u5F15\u64CE\u8FDB\u7A0B\u672A\u54CD\u5E94\u5F3A\u5236\u7EC8\u6B62\uFF0C\u653E\u5F03\u7B49\u5F85\uFF08\u53EF\u80FD\u6B8B\u7559\u5B64\u513F\u8FDB\u7A0B\uFF09",{projectId:this.options.projectId,pid:t,generation:n}),this.child?.gen===n&&(this.child=null))}spawnChild(){this.childGen+=1;let e=this.childGen,t=["run","--config",this.options.workspaceDir,"--emit-events","ipc"],n=this.doSpawn({command:process.execPath,args:[this.options.cliPath,...t],cwd:this.options.workspaceDir,stdio:["ignore","pipe","pipe","ipc"],...this.options.env?{env:this.options.env}:{}}),o=n.child,i={managed:n,gen:e,startedAt:Date.now(),stopping:!1};this.child=i,_.info(O,"engine started",{projectId:this.options.projectId,pid:o.pid,generation:e}),o.on("message",s=>{oo(s)&&this.options.onRunEvent?.(s.event)}),o.on("error",s=>{this.lastError=s.message,_.error(O,"engine spawn error",{projectId:this.options.projectId,error:s.message})}),o.on("exit",(s,a)=>this.handleExit(i,s,a))}handleExit(e,t,n){if(this.child?.gen===e.gen&&(this.child=null),this.lastExitCode=t,_.info(O,"engine exited",{projectId:this.options.projectId,generation:e.gen,code:t,signal:n}),e.stopping||this.stopped)return;this.consecutiveFailures+=1;let o=this.options.maxConsecutiveFailures??5;if(this.consecutiveFailures>=o){_.error(O,"engine crash loop\uFF0C\u505C\u6B62\u91CD\u542F\u5E76\u4E0A\u62A5",{projectId:this.options.projectId,failures:this.consecutiveFailures}),this.stopped=!0,this.options.onCrashLoop?.({projectId:this.options.projectId,failures:this.consecutiveFailures,lastExitCode:t});return}let i=this.options.restartBackoffMs??1e3,s=this.options.restartBackoffMaxMs??6e4,a=Math.min(s,i*2**(this.consecutiveFailures-1));_.warn(O,"engine \u5F02\u5E38\u9000\u51FA\uFF0C\u9000\u907F\u91CD\u542F",{projectId:this.options.projectId,failures:this.consecutiveFailures,delayMs:a}),this.restartTimer=this.setTimer(()=>{this.restartTimer=null,!this.stopped&&!this.child&&this.spawnChild()},a)}markHealthy(){this.consecutiveFailures=0}};function oo(r){return typeof r=="object"&&r!==null&&r.type==="run-event"&&"event"in r}c(oo,"isRunEventMessage");import{appendFile as ro,mkdir as io,rename as so,stat as ao}from"fs/promises";import co from"path";import{logger as re}from"aitable-workflow-core";var ie="event-spool",se=class{static{c(this,"EventSpool")}options;setInterval;clearInterval;now;buffer=[];pending=[];seq=0;timer=null;flushing=!1;constructor(e){this.options=e,this.setInterval=e.setIntervalImpl??((t,n)=>setInterval(t,n)),this.clearInterval=e.clearIntervalImpl??(t=>clearInterval(t)),this.now=e.now??Date.now}get lastSeq(){return this.seq}get pendingCount(){return this.pending.length}start(){this.timer||(this.timer=this.setInterval(()=>{this.flush()},this.options.flushIntervalMs??500))}async stop(){this.timer&&(this.clearInterval(this.timer),this.timer=null),await this.flush()}append(e){this.seq+=1;let t={seq:this.seq,ts:new Date(this.now()).toISOString(),event:e};this.pending.push(t),this.buffer.push(t);let n=this.options.maxPending??5e3;if(this.pending.length>n){let i=this.pending.splice(0,this.pending.length-n);re.warn(ie,"\u5F85\u53D1\u4E8B\u4EF6\u8D85\u4E0A\u9650\uFF0C\u4E22\u5F03\u6700\u65E7\u7684\u6279\u6B21",{projectId:this.options.projectId,dropped:i.length,untilSeq:i[i.length-1]?.seq})}let o=this.options.retain??2e3;this.buffer.length>o&&this.buffer.splice(0,this.buffer.length-o),this.options.persist!==!1&&this.persist(t)}async flush(){if(this.flushing||this.pending.length===0)return;this.flushing=!0;let e=this.pending.slice(0,this.options.maxBatch??200);try{await this.options.send(e);let t=new Set(e.map(n=>n.seq));this.pending=this.pending.filter(n=>!t.has(n.seq))}catch(t){re.warn(ie,"\u4E8B\u4EF6\u6279\u4E0A\u884C\u5931\u8D25\uFF08\u4FDD\u7559\u5F85\u4E0B\u8F6E\u91CD\u8BD5\uFF09",{projectId:this.options.projectId,count:e.length,error:t instanceof Error?t.message:String(t)})}finally{this.flushing=!1}}replayFrom(e){let t=new Set(this.pending.map(o=>o.seq)),n=this.buffer.filter(o=>o.seq>e&&!t.has(o.seq));return n.length===0?0:(this.pending=[...this.pending,...n].sort((o,i)=>o.seq-i.seq),re.info(ie,"\u8865\u4F20\u7F3A\u53E3\u4E8B\u4EF6",{projectId:this.options.projectId,serverLastSeq:e,replay:n.length}),n.length)}async persist(e){let t=co.join(this.options.spoolDir,"run-events.jsonl");try{await io(this.options.spoolDir,{recursive:!0}),await ao(t).then(o=>o.size).catch(()=>0)>(this.options.rotateBytes??10*1024*1024)&&await so(t,`${t}.1`).catch(()=>{}),await ro(t,`${JSON.stringify(e)}
|
|
19
|
-
`,"utf8")}catch(n){re.warn(ie,"spool \u843D\u76D8\u5931\u8D25",{projectId:this.options.projectId,error:n instanceof Error?n.message:String(n)})}}};import{readdir as
|
|
18
|
+
`);for(let i of n){let s=i.installed?"\u2713":"\u2717",a=[i.bin,...i.fallbackBins].join(" / "),l=i.installed?`\u5DF2\u5B89\u88C5 (${i.detectedCmd}${i.detectedVersion?` ${i.detectedVersion}`:""})`:"\u672A\u63A2\u6D4B\u5230";console.log(` ${s} ${i.name.padEnd(16)} [${i.id}] bin: ${a.padEnd(24)} ${l}`)}console.log("")}catch(n){Gn.error("cli","\u5217\u51FA agents \u5931\u8D25",{error:n instanceof Error?n.message:String(n)}),process.exit(1)}})}c(gt,"registerAgentsCommand");import{logger as Hn}from"aitable-workflow-core";import{startServer as qn,resolveServeConfig as Kn}from"aitable-workflow-server";import te from"path";import{existsSync as ut}from"fs";function ft(r,e){r.command("serve").description("\u542F\u52A8\u672C\u5730 HTTP API \u670D\u52A1\u5E76\u6258\u7BA1 Web \u524D\u7AEF\u9875\u9762\uFF08local \u6A21\u5F0F\u81EA\u52A8\u6253\u5F00\u6D4F\u89C8\u5668\uFF09").option("-p, --port <number>","\u76D1\u542C\u7AEF\u53E3\uFF08\u9ED8\u8BA4 8787\uFF0C\u53EF\u7528 AITABLE_WORKFLOW_PORT \u8986\u76D6\uFF09").option("--host <host>","\u76D1\u542C\u5730\u5740\uFF08\u9ED8\u8BA4 127.0.0.1\uFF0C\u53EF\u7528 AITABLE_WORKFLOW_HOST \u8986\u76D6\uFF09").option("--data-root <path>","\u9879\u76EE\u5DE5\u4F5C\u533A\u6570\u636E\u76EE\u5F55\uFF08\u9ED8\u8BA4 .sandbox/aitable-workflow-server\uFF0C\u53EF\u7528 AITABLE_WORKFLOW_DATA_DIR \u8986\u76D6\uFF09").option("--web-root <path>","Web \u524D\u7AEF\u9759\u6001\u8D44\u6E90\u76EE\u5F55\uFF08\u9ED8\u8BA4\u4F7F\u7528\u968F CLI \u6253\u5305\u7684 dist/web\uFF09").option("--no-open","\u542F\u52A8\u540E\u4E0D\u81EA\u52A8\u6253\u5F00\u6D4F\u89C8\u5668\uFF08\u9ED8\u8BA4\u81EA\u52A8\u6253\u5F00\uFF09").action(async t=>{try{let n={};if(t.port!==void 0){let i=Number.parseInt(t.port,10);Number.isFinite(i)&&i>0&&(n.port=i)}if(t.host!==void 0&&(n.host=t.host),t.dataRoot!==void 0&&(n.dataRoot=t.dataRoot),t.webRoot!==void 0)n.webRoot=t.webRoot;else{let i=te.join(e.cliDir,"web");if(ut(te.join(i,"index.html")))n.webRoot=i;else{let s=te.resolve(e.cliDir,"..","..","web","dist");ut(te.join(s,"index.html"))&&(n.webRoot=s)}}let o=Kn(n);if(await qn(o),o.webRoot&&t.open!==!1){let i=o.host==="0.0.0.0"?"127.0.0.1":o.host;Ge(`http://${i}:${o.port}`)}}catch(n){Hn.error("cli","HTTP \u670D\u52A1\u542F\u52A8\u5931\u8D25",{error:n instanceof Error?n.message:String(n)}),process.exit(1)}})}c(ft,"registerServeCommand");import{Command as _e}from"commander";import{logger as A,listCredentials as Bt,getActiveCredential as j,maskSecret as sr}from"aitable-workflow-core";import F from"os";import M from"path";import{mkdir as _o,readFile as Lo}from"fs/promises";import{mkdirSync as Mo}from"fs";import{logger as w,detectInstalledAgents as Fo,runCodingAgent as Bo,DwsClient as Uo,getActiveCredential as No,assertPortableId as Pt,resolveInsideRoot as $t,probeCommand as Wo}from"aitable-workflow-core";import Re from"ws";import{logger as x,RELAY_REJECTED_CODE as Vn,isRelayMessage as Yn}from"aitable-workflow-core";var T="relay:conn",ht=1e3,Jn=3e4,zn=Number(process.env.AITABLE_RELAY_SERVER_STALE_MS??9e4),ne=class{static{c(this,"RelayConnection")}ws=null;delay=ht;timer=null;watchdog=null;should=!0;permanentlyRejected=!1;accepted=!1;lastServerFrameAt=0;serverUrl;key;token;onMessage;onOpen;onAccepted;onPermanentReject;mkWs;staleMs;constructor(e){this.serverUrl=e.serverUrl,this.key=e.key??"",this.token=e.token??"",this.onMessage=e.onMessage,this.onOpen=e.onOpen,this.onAccepted=e.onAccepted,this.onPermanentReject=e.onPermanentReject,this.mkWs=e.mkWs??(t=>new Re(t)),this.staleMs=e.staleMs??zn}connect(){this.permanentlyRejected||(this.should=!0,this.doConnect())}send(e){this.ws&&this.ws.readyState===Re.OPEN&&this.ws.send(JSON.stringify(e))}close(){this.should=!1,this.timer&&(clearTimeout(this.timer),this.timer=null),this.watchdog&&(clearTimeout(this.watchdog),this.watchdog=null),this.ws?.close()}isPermanentlyRejected(){return this.permanentlyRejected}currentSocket(){return this.ws}buildUrl(){let e=this.serverUrl.replace(/\/+$/,"").replace(/^http/,"ws"),t=this.token?`token=${encodeURIComponent(this.token)}`:`key=${encodeURIComponent(this.key)}`;return`${e}/api/relay/connect?${t}`}doConnect(){if(!this.should||this.permanentlyRejected)return;let e=this.buildUrl();x.info(T,"connecting",{serverUrl:this.serverUrl}),this.watchdog&&(clearTimeout(this.watchdog),this.watchdog=null),this.accepted=!1,this.lastServerFrameAt=0;let t=this.mkWs(e);this.ws=t,t.on("open",()=>{x.info(T,"connected"),this.onOpen?.(t)}),t.on("message",n=>{this.accepted||(this.accepted=!0,this.delay=ht,this.onAccepted?.(t)),this.lastServerFrameAt=Date.now(),this.armWatchdog();let o;try{o=JSON.parse(typeof n=="string"?n:String(n))}catch{return}Yn(o)&&(o.type==="ping"&&this.send({type:"pong"}),this.onMessage(o))}),t.on("close",(n,o)=>{this.watchdog&&(clearTimeout(this.watchdog),this.watchdog=null);let i=o==null?"":String(o);if(n===Vn){this.permanentlyRejected=!0,this.should=!1,x.error(T,"server permanently rejected this machine: its connection key is unknown or was removed; re-issue the relay connect command and restart with the new key",{code:n,reason:i}),this.onPermanentReject?.({code:n,reason:i});return}x.warn(T,"disconnected",{code:n}),this.scheduleReconnect()}),t.on("error",n=>{x.error(T,"ws error",{detail:n instanceof Error?n.message:String(n)})})}armWatchdog(){this.watchdog&&clearTimeout(this.watchdog),this.watchdog=setTimeout(()=>{if(!(!this.accepted||!this.lastServerFrameAt||this.ws?.readyState!==Re.OPEN)){x.warn(T,"server heartbeat stale; closing socket to reconnect",{staleMs:Date.now()-this.lastServerFrameAt});try{this.ws.close()}catch{}}},this.staleMs+1),this.watchdog.unref?.()}scheduleReconnect(){!this.should||this.permanentlyRejected||this.timer||(x.info(T,"reconnecting",{ms:this.delay}),this.timer=setTimeout(()=>{this.timer=null,this.doConnect()},this.delay),this.timer.unref?.(),this.delay=Math.min(this.delay*2,Jn))}};import{createHash as Xn}from"crypto";import{readdir as Qn,readFile as Zn,stat as eo}from"fs/promises";import wt from"path";import{isAllowedArtifactPath as to,normalizeArtifactRelPath as no}from"aitable-workflow-core";var yt={async upload(r,e,t){let n=await fetch(r,{method:"PUT",body:e,headers:{...t}});if(!n.ok)throw new Error(`artifact PUT failed: ${n.status} ${n.statusText}`)}};async function vt(r){let e=[];return await kt(r,r,e),e.sort((t,n)=>t.relPath.localeCompare(n.relPath))}c(vt,"scanArtifacts");async function kt(r,e,t){let n;try{n=await Qn(e,{withFileTypes:!0})}catch{return}for(let o of n){if(o.name==="node_modules"||o.name.startsWith("."))continue;let i=wt.join(e,o.name);if(o.isDirectory())await kt(r,i,t);else if(o.isFile()){let s=wt.relative(r,i).replace(/\\/g,"/");if(!to(s))continue;let a=await eo(i),l=await Zn(i),p=Xn("sha256").update(l).digest("hex");t.push({relPath:no(s),absPath:i,size:a.size,sha256:p})}}}c(kt,"walk");import Tt from"path";import{logger as k}from"aitable-workflow-core";import{logger as D,spawnManagedProcess as oo}from"aitable-workflow-core";var _="engine-supervisor",oe=class{static{c(this,"EngineSupervisor")}options;doSpawn;setTimer;clearTimer;child=null;childGen=0;consecutiveFailures=0;lastExitCode=null;lastError=null;restartTimer=null;stopped=!0;constructor(e){this.options=e,this.doSpawn=e.spawnImpl??oo,this.setTimer=e.setTimeoutImpl??((t,n)=>setTimeout(t,n)),this.clearTimer=e.clearTimeoutImpl??(t=>clearTimeout(t))}get status(){return{running:this.child!=null,pid:this.child?.managed.child.pid??null,generation:this.childGen,startedAt:this.child?new Date(this.child.startedAt).toISOString():null,consecutiveFailures:this.consecutiveFailures,lastExitCode:this.lastExitCode,lastError:this.lastError}}start(){this.stopped=!1,!this.child&&this.spawnChild()}async stop(){this.stopped=!0,this.restartTimer&&(this.clearTimer(this.restartTimer),this.restartTimer=null);let e=this.child;if(!e)return;e.stopping=!0;let t=e.managed.child.pid;if(t==null){this.child=null;return}let n=e.gen;await e.managed.terminateTree({gracePeriodMs:this.options.drainTimeoutMs??2e4}),e.managed.child.exitCode==null&&e.managed.child.signalCode==null&&(D.error(_,"\u5F15\u64CE\u8FDB\u7A0B\u672A\u54CD\u5E94\u5F3A\u5236\u7EC8\u6B62\uFF0C\u653E\u5F03\u7B49\u5F85\uFF08\u53EF\u80FD\u6B8B\u7559\u5B64\u513F\u8FDB\u7A0B\uFF09",{projectId:this.options.projectId,pid:t,generation:n}),this.child?.gen===n&&(this.child=null))}spawnChild(){this.childGen+=1;let e=this.childGen,t=["run","--config",this.options.workspaceDir,"--emit-events","ipc"],n=this.doSpawn({command:process.execPath,args:[this.options.cliPath,...t],cwd:this.options.workspaceDir,stdio:["ignore","pipe","pipe","ipc"],...this.options.env?{env:this.options.env}:{}}),o=n.child,i={managed:n,gen:e,startedAt:Date.now(),stopping:!1};this.child=i,D.info(_,"engine started",{projectId:this.options.projectId,pid:o.pid,generation:e}),o.on("message",s=>{ro(s)&&this.options.onRunEvent?.(s.event)}),o.on("error",s=>{this.lastError=s.message,D.error(_,"engine spawn error",{projectId:this.options.projectId,error:s.message})}),o.on("exit",(s,a)=>this.handleExit(i,s,a))}handleExit(e,t,n){if(this.child?.gen===e.gen&&(this.child=null),this.lastExitCode=t,D.info(_,"engine exited",{projectId:this.options.projectId,generation:e.gen,code:t,signal:n}),e.stopping||this.stopped)return;this.consecutiveFailures+=1;let o=this.options.maxConsecutiveFailures??5;if(this.consecutiveFailures>=o){D.error(_,"engine crash loop\uFF0C\u505C\u6B62\u91CD\u542F\u5E76\u4E0A\u62A5",{projectId:this.options.projectId,failures:this.consecutiveFailures}),this.stopped=!0,this.options.onCrashLoop?.({projectId:this.options.projectId,failures:this.consecutiveFailures,lastExitCode:t});return}let i=this.options.restartBackoffMs??1e3,s=this.options.restartBackoffMaxMs??6e4,a=Math.min(s,i*2**(this.consecutiveFailures-1));D.warn(_,"engine \u5F02\u5E38\u9000\u51FA\uFF0C\u9000\u907F\u91CD\u542F",{projectId:this.options.projectId,failures:this.consecutiveFailures,delayMs:a}),this.restartTimer=this.setTimer(()=>{this.restartTimer=null,!this.stopped&&!this.child&&this.spawnChild()},a)}markHealthy(){this.consecutiveFailures=0}};function ro(r){return typeof r=="object"&&r!==null&&r.type==="run-event"&&"event"in r}c(ro,"isRunEventMessage");import{appendFile as io,mkdir as so,rename as ao,stat as co}from"fs/promises";import lo from"path";import{logger as re}from"aitable-workflow-core";var ie="event-spool",se=class{static{c(this,"EventSpool")}options;setInterval;clearInterval;now;buffer=[];pending=[];seq=0;timer=null;flushing=!1;constructor(e){this.options=e,this.setInterval=e.setIntervalImpl??((t,n)=>setInterval(t,n)),this.clearInterval=e.clearIntervalImpl??(t=>clearInterval(t)),this.now=e.now??Date.now}get lastSeq(){return this.seq}get pendingCount(){return this.pending.length}start(){this.timer||(this.timer=this.setInterval(()=>{this.flush()},this.options.flushIntervalMs??500))}async stop(){this.timer&&(this.clearInterval(this.timer),this.timer=null),await this.flush()}append(e){this.seq+=1;let t={seq:this.seq,ts:new Date(this.now()).toISOString(),event:e};this.pending.push(t),this.buffer.push(t);let n=this.options.maxPending??5e3;if(this.pending.length>n){let i=this.pending.splice(0,this.pending.length-n);re.warn(ie,"\u5F85\u53D1\u4E8B\u4EF6\u8D85\u4E0A\u9650\uFF0C\u4E22\u5F03\u6700\u65E7\u7684\u6279\u6B21",{projectId:this.options.projectId,dropped:i.length,untilSeq:i[i.length-1]?.seq})}let o=this.options.retain??2e3;this.buffer.length>o&&this.buffer.splice(0,this.buffer.length-o),this.options.persist!==!1&&this.persist(t)}async flush(){if(this.flushing||this.pending.length===0)return;this.flushing=!0;let e=this.pending.slice(0,this.options.maxBatch??200);try{await this.options.send(e);let t=new Set(e.map(n=>n.seq));this.pending=this.pending.filter(n=>!t.has(n.seq))}catch(t){re.warn(ie,"\u4E8B\u4EF6\u6279\u4E0A\u884C\u5931\u8D25\uFF08\u4FDD\u7559\u5F85\u4E0B\u8F6E\u91CD\u8BD5\uFF09",{projectId:this.options.projectId,count:e.length,error:t instanceof Error?t.message:String(t)})}finally{this.flushing=!1}}replayFrom(e){let t=new Set(this.pending.map(o=>o.seq)),n=this.buffer.filter(o=>o.seq>e&&!t.has(o.seq));return n.length===0?0:(this.pending=[...this.pending,...n].sort((o,i)=>o.seq-i.seq),re.info(ie,"\u8865\u4F20\u7F3A\u53E3\u4E8B\u4EF6",{projectId:this.options.projectId,serverLastSeq:e,replay:n.length}),n.length)}async persist(e){let t=lo.join(this.options.spoolDir,"run-events.jsonl");try{await so(this.options.spoolDir,{recursive:!0}),await co(t).then(o=>o.size).catch(()=>0)>(this.options.rotateBytes??10*1024*1024)&&await ao(t,`${t}.1`).catch(()=>{}),await io(t,`${JSON.stringify(e)}
|
|
19
|
+
`,"utf8")}catch(n){re.warn(ie,"spool \u843D\u76D8\u5931\u8D25",{projectId:this.options.projectId,error:n instanceof Error?n.message:String(n)})}}};import{readdir as xe,rm as Ee,stat as le}from"fs/promises";import L from"path";import{logger as W}from"aitable-workflow-core";import{createHash as wo}from"crypto";import{mkdir as Ae,readFile as yo,rename as vo,rm as ko,writeFile as It}from"fs/promises";import ae from"path";import{decodeWorkspaceBundle as bo,logger as Co,resolveInsideRoot as Ro}from"aitable-workflow-core";import{chmod as po,readFile as mo,rename as go,writeFile as uo}from"fs/promises";var bt="# \u2500\u2500\u2500 aitable-workflow managed credentials (auto-generated, do not edit) \u2500\u2500\u2500",Ct="# \u2500\u2500\u2500 end managed credentials \u2500\u2500\u2500";function fo(r,e){let t=r.split(`
|
|
20
20
|
`),n=t.indexOf(bt),o=t.indexOf(Ct),i;n!==-1&&o!==-1&&o>n?i=[...t.slice(0,n),...t.slice(o+1)].join(`
|
|
21
|
-
`):i=r,i=i.replace(/^\n+/,"");let s=Object.entries(e).sort(([l],[
|
|
21
|
+
`):i=r,i=i.replace(/^\n+/,"");let s=Object.entries(e).sort(([l],[p])=>l.localeCompare(p));return s.length===0?i:`${[bt,...s.map(([l,p])=>`${l}=${ho(p)}`),Ct].join(`
|
|
22
22
|
`)}
|
|
23
23
|
${i?`
|
|
24
|
-
${i}`:""}`}c(
|
|
25
|
-
`,"utf8"),{projectId:e,revision:i.revision,action:l,written:s,deleted:a,envUpdated:u}}c(St,"syncWorkspace");var G="runner-gc";async function xt(r){let e=0,t;try{t=await Ee(r,{withFileTypes:!0})}catch{return 0}for(let n of t){let o=L.join(r,n.name);if(!n.isSymbolicLink()){if(n.isDirectory()){e+=await xt(o);continue}e+=await le(o).then(i=>i.size).catch(()=>0)}}return e}c(xt,"dirSize");async function xo(r){let e=L.join(r,"projects"),t;try{t=await Ee(e,{withFileTypes:!0})}catch{return[]}let n=[];for(let o of t){if(!o.isDirectory())continue;let i=L.join(e,o.name),s=await le(L.join(i,ce)).then(l=>l.mtimeMs).catch(()=>null),a=await le(i).then(l=>l.mtimeMs).catch(()=>0);n.push({projectId:o.name,dir:i,lastTouchedMs:s??a,sizeBytes:await xt(i)})}return n}c(xo,"scanProjects");async function At(r,e){let t=L.join(r,"traces"),n;try{n=await Ee(t,{withFileTypes:!0})}catch{return 0}let o=[];for(let a of n){if(!a.isFile())continue;let l=L.join(t,a.name),m=await le(l).catch(()=>null);m&&o.push({full:l,size:m.size,mtimeMs:m.mtimeMs})}let i=o.reduce((a,l)=>a+l.size,0);if(i<=e)return 0;o.sort((a,l)=>a.mtimeMs-l.mtimeMs);let s=0;for(let a of o){if(i<=e)break;await xe(a.full,{force:!0}).catch(()=>{}),i-=a.size,s+=a.size}return s}c(At,"pruneTraces");async function Et(r){let e=(r.now??Date.now)(),t=(r.ttlDays??14)*24*60*60*1e3,n=r.tracesMaxBytes??500*1024*1024,o=r.maxDiskBytes??20*1024*1024*1024,i=new Set(r.activeProjectIds),s=await xo(r.workspaceRoot),a=[],l=0;for(let u of s){if(i.has(u.projectId)){l+=await At(u.dir,n);continue}if(e-u.lastTouchedMs>=t){await xe(u.dir,{recursive:!0,force:!0}).catch(p=>{N.warn(G,"\u9879\u76EE\u76EE\u5F55\u56DE\u6536\u5931\u8D25",{projectId:u.projectId,error:p instanceof Error?p.message:String(p)})}),a.push(u.projectId),N.info(G,"\u56DE\u6536\u8FC7\u671F\u9879\u76EE\u76EE\u5F55",{projectId:u.projectId,idleDays:Math.floor((e-u.lastTouchedMs)/864e5)});continue}l+=await At(u.dir,n)}let m=s.filter(u=>!a.includes(u.projectId)),d=m.reduce((u,p)=>u+p.sizeBytes,0)-l,g=!1;if(d>o){g=!0,N.warn(G,"\u5DE5\u4F5C\u533A\u8D85\u78C1\u76D8\u6C34\u4F4D\uFF0C\u5F00\u59CB\u56DE\u6536\u6700\u4E45\u672A\u8BA4\u9886\u9879\u76EE",{totalBytes:d,maxDiskBytes:o});let u=m.filter(p=>!i.has(p.projectId)).sort((p,v)=>p.lastTouchedMs-v.lastTouchedMs);for(let p of u){if(d<=o)break;await xe(p.dir,{recursive:!0,force:!0}).catch(()=>{}),a.push(p.projectId),d-=p.sizeBytes,N.warn(G,"\u6C34\u4F4D\u56DE\u6536\u9879\u76EE\u76EE\u5F55",{projectId:p.projectId,freedBytes:p.sizeBytes})}d>o&&N.error(G,"\u56DE\u6536\u540E\u4ECD\u8D85\u78C1\u76D8\u6C34\u4F4D\uFF08\u6D3B\u8DC3\u9879\u76EE\u5360\u7528\uFF09\uFF0C\u9700\u4EBA\u5DE5\u4ECB\u5165",{totalBytes:d,maxDiskBytes:o})}return{removedProjects:a,tracesFreedBytes:l,diskPressure:g,totalBytes:Math.max(0,d)}}c(Et,"runGc");var b="runner-loop",me=class{static{c(this,"RunnerLoop")}options;maxWorkflows;setTimer;clearTimer;doSync;holdings=new Map;pollTimer=null;gcTimer=null;running=!1;claiming=!1;constructor(e){this.options=e,this.maxWorkflows=Math.max(1,Math.floor(e.maxWorkflows??3)),this.setTimer=e.setTimeoutImpl??((t,n)=>setTimeout(t,n)),this.clearTimer=e.clearTimeoutImpl??(t=>clearTimeout(t)),this.doSync=e.syncImpl??St}get held(){return[...this.holdings.values()].map(e=>({projectId:e.projectId,generation:e.generation,revision:e.revision,engine:e.engine.status}))}get freeSlots(){return Math.max(0,this.maxWorkflows-this.holdings.size)}start(){this.running||(this.running=!0,k.info(b,"runner loop \u542F\u52A8",{maxWorkflows:this.maxWorkflows,workspaceRoot:this.options.workspaceRoot}),this.claimOnce(),this.scheduleGc(0))}async stop(){this.running=!1,this.pollTimer&&(this.clearTimer(this.pollTimer),this.pollTimer=null),this.gcTimer&&(this.clearTimer(this.gcTimer),this.gcTimer=null),await Promise.all([...this.holdings.keys()].map(e=>this.releaseHolding(e,"daemon_shutdown")))}scheduleGc(e){let t=this.options.gcIntervalMs??864e5;!this.running||t<=0||(this.gcTimer&&this.clearTimer(this.gcTimer),this.gcTimer=this.setTimer(()=>{this.gcTimer=null,this.gcOnce().finally(()=>this.scheduleGc(t))},e))}async gcOnce(){try{let e=await(this.options.gcImpl??Et)({workspaceRoot:this.options.workspaceRoot,activeProjectIds:[...this.holdings.keys()],...this.options.gcOptions??{}});(e.removedProjects.length>0||e.tracesFreedBytes>0)&&k.info(b,"\u672C\u673A\u56DE\u6536\u5B8C\u6210",{removed:e.removedProjects.length,tracesFreedBytes:e.tracesFreedBytes,totalBytes:e.totalBytes})}catch(e){k.warn(b,"\u672C\u673A\u56DE\u6536\u5931\u8D25",{error:$(e)})}}onWakeup(){this.running&&this.claimOnce()}async claimOnce(){if(!this.running||this.claiming)return;this.claiming=!0;let e=this.options.defaultPollMs??1e4;try{let t=this.freeSlots;if(t>0){let n=await this.post("/api/runner/workflows/claim",{capacity:t});typeof n.nextPollAfterMs=="number"&&n.nextPollAfterMs>0&&(e=n.nextPollAfterMs);for(let o of n.grants??[])await this.acceptGrant(o,n.heartbeatIntervalSeconds)}}catch(t){k.warn(b,"claim \u5931\u8D25\uFF08\u9000\u907F\u540E\u91CD\u8BD5\uFF09",{error:$(t)}),e=Math.max(e,5e3)}finally{this.claiming=!1,this.scheduleNextPoll(e)}}scheduleNextPoll(e){this.running&&(this.pollTimer&&this.clearTimer(this.pollTimer),this.pollTimer=this.setTimer(()=>{this.pollTimer=null,this.claimOnce()},e))}async acceptGrant(e,t){if(this.holdings.has(e.projectId))return;let n=Tt.join(this.options.workspaceRoot,"projects",e.projectId);k.info(b,"\u8BA4\u9886\u9879\u76EE",{projectId:e.projectId,generation:e.generation,workspaceDir:n});let o;try{o=await this.doSync(this.clientConfig(),e.projectId,n)}catch(l){k.error(b,"\u5DE5\u4F5C\u533A\u540C\u6B65\u5931\u8D25\uFF0C\u4EA4\u8FD8\u79DF\u7EA6",{projectId:e.projectId,error:$(l)}),await this.postSafe(`/api/runner/workflows/${e.projectId}/release`,{generation:e.generation,reason:"workspace_sync_failed"});return}let i=(this.options.createSupervisor??Eo)({projectId:e.projectId,workspaceDir:n,cliPath:this.options.cliPath,onRunEvent:c(l=>this.handleRunEvent(e.projectId,l),"onRunEvent"),onCrashLoop:c(()=>{this.releaseHolding(e.projectId,"engine_crash_loop")},"onCrashLoop")}),s=this.options.sendRunEvents?new se({projectId:e.projectId,spoolDir:n,send:c(l=>this.options.sendRunEvents(e.projectId,l),"send")}):null;s?.start();let a={projectId:e.projectId,generation:e.generation,revision:o.revision,engine:i,spool:s,heartbeatTimer:null,busy:!1};this.holdings.set(e.projectId,a),i.start(),this.scheduleHeartbeat(a,(t??15)*1e3)}handleRunEvent(e,t){this.holdings.get(e)?.spool?.append(t),this.options.onRunEvent?.(e,t)}scheduleHeartbeat(e,t){this.running&&(e.heartbeatTimer&&this.clearTimer(e.heartbeatTimer),e.heartbeatTimer=this.setTimer(()=>{e.heartbeatTimer=null,this.heartbeatOnce(e)},t))}async heartbeatOnce(e){if(!this.running||e.busy||!this.holdings.has(e.projectId))return;let t=this.options.defaultHeartbeatMs??15e3;try{let n=await this.post(`/api/runner/workflows/${e.projectId}/heartbeat`,{generation:e.generation,workspaceRevision:e.revision,engineStatus:e.engine.status,...e.spool?{lastEventSeq:e.spool.lastSeq}:{}}),o=typeof n.nextIntervalSeconds=="number"&&n.nextIntervalSeconds>0?n.nextIntervalSeconds*1e3:t;if(e.spool&&typeof n.lastEventSeq=="number"&&e.spool.replayFrom(n.lastEventSeq),n.action==="stop"){k.info(b,"\u5FC3\u8DF3\u4E0B\u53D1 stop\uFF0C\u505C\u6B62\u5F15\u64CE",{projectId:e.projectId}),await this.releaseHolding(e.projectId,"heartbeat_stop");return}if(n.action==="resync"){await this.resyncHolding(e)&&this.scheduleHeartbeat(e,o);return}e.engine.markHealthy(),this.scheduleHeartbeat(e,o)}catch(n){k.warn(b,"\u5FC3\u8DF3\u5931\u8D25\uFF08\u4FDD\u6301\u5F15\u64CE\u8FD0\u884C\uFF0C\u7B49\u5F85\u6062\u590D\u6216\u79DF\u7EA6\u8FC7\u671F\uFF09",{projectId:e.projectId,error:$(n)}),this.scheduleHeartbeat(e,Math.min(t,5e3))}}async resyncHolding(e){if(e.busy)return!0;e.busy=!0;let t=Tt.join(this.options.workspaceRoot,"projects",e.projectId);try{k.info(b,"\u5DE5\u4F5C\u533A\u9700\u91CD\u540C\u6B65\uFF0C\u91CD\u542F\u5F15\u64CE",{projectId:e.projectId}),await e.engine.stop();let n=await this.doSync(this.clientConfig(),e.projectId,t);e.revision=n.revision,e.engine.start()}catch(n){return k.error(b,"\u91CD\u540C\u6B65\u5931\u8D25\uFF0C\u4EA4\u8FD8\u79DF\u7EA6",{projectId:e.projectId,error:$(n)}),e.busy=!1,await this.releaseHolding(e.projectId,"resync_failed"),!1}return e.busy=!1,!0}async releaseHolding(e,t){let n=this.holdings.get(e);n&&(this.holdings.delete(e),n.heartbeatTimer&&(this.clearTimer(n.heartbeatTimer),n.heartbeatTimer=null),await n.engine.stop().catch(o=>{k.warn(b,"\u505C\u6B62\u5F15\u64CE\u5F02\u5E38",{projectId:e,error:$(o)})}),await n.spool?.stop().catch(()=>{}),await this.postSafe(`/api/runner/workflows/${e}/release`,{generation:n.generation,reason:t}),k.info(b,"\u5DF2\u8BA9\u51FA\u79DF\u7EA6",{projectId:e,reason:t}),this.running&&this.scheduleNextPoll(this.options.defaultPollMs??1e4))}clientConfig(){return{gatewayUrl:this.options.gatewayUrl,machineKey:this.options.machineKey,...this.options.fetchImpl?{fetchImpl:this.options.fetchImpl}:{}}}async post(e,t){let o=await(this.options.fetchImpl??fetch)(`${this.options.gatewayUrl.replace(/\/+$/,"")}${e}`,{method:"POST",headers:{authorization:`Bearer ${this.options.machineKey}`,"content-type":"application/json"},body:JSON.stringify(t??{})});if(!o.ok){let i=await o.text().catch(()=>"");throw new Error(`runner API ${e} \u5931\u8D25: HTTP ${o.status} ${i.slice(0,200)}`)}return await o.json()}async postSafe(e,t){try{await this.post(e,t)}catch(n){k.warn(b,"\u6536\u5C3E\u8BF7\u6C42\u5931\u8D25\uFF08\u79DF\u7EA6\u5C06\u7531\u670D\u52A1\u7AEF\u8FC7\u671F\u56DE\u6536\uFF09",{pathname:e,error:$(n)})}}};function Eo(r){return new oe({projectId:r.projectId,workspaceDir:r.workspaceDir,cliPath:r.cliPath,...r.onRunEvent?{onRunEvent:r.onRunEvent}:{},...r.onCrashLoop?{onCrashLoop:c(()=>r.onCrashLoop(),"onCrashLoop")}:{}})}c(Eo,"defaultSupervisorFactory");function $(r){return r instanceof Error?r.message:String(r)}c($,"describeError");import{spawn as $o}from"child_process";import{existsSync as Po}from"fs";import q from"path";import{fileURLToPath as jo}from"url";import{appendFileSync as $s,statSync as Ps,renameSync as js,mkdirSync as Ds,existsSync as _s}from"fs";import To from"path";var Ms=10*1024*1024;function H(r){return To.join(X(r),"daemon.log")}c(H,"logFilePath");var pe=q.dirname(jo(import.meta.url));function Te(){let r=[q.resolve(pe,"..","..","bin","aitable-workflow.mjs"),q.resolve(pe,"..","bin","aitable-workflow.mjs"),q.resolve(pe,"..","..","dist","cli.js"),q.resolve(pe,"cli.js")];return r.find(e=>Po(e))??r[0]}c(Te,"resolveDaemonEntry");var Do=Te();async function K(r){await qe(r.agentId);let e=["relay"];r.serverUrl&&e.push("--server-url",r.serverUrl),r.token&&e.push("--token",r.token),r.key&&e.push("--key",r.key),r.name&&e.push("--name",r.name),r.workspaceRoot&&e.push("--workspace-root",r.workspaceRoot),r.machineId&&e.push("--machine-id",r.machineId),r.maxTasks&&e.push("--max-tasks",String(r.maxTasks));let t=await import("fs").then(s=>{let a=H(r.agentId);return s.openSync(a,"a")}),n=$o(process.execPath,[Do,...e],{detached:!0,stdio:["ignore",t,t],env:{...process.env}});try{let{closeSync:s}=await import("fs");s(t)}catch{}n.unref();let{writeFile:o}=await import("fs/promises"),{pidFilePath:i}=await import("./pid-file-KT5KHCKU.js");return n.pid&&await o(i(r.agentId),String(n.pid),"utf8"),{pid:n.pid??0}}c(K,"startDaemon");async function de(r,e=5e3){let t=await ve(r);if(t===null||!W(t))return await Q(r),!1;try{process.kill(t,"SIGTERM")}catch{return await Q(r),!1}let n=Date.now()+e;for(;Date.now()<n&&W(t);)await new Promise(o=>setTimeout(o,200));if(W(t))try{process.kill(t,"SIGKILL")}catch{}return await Q(r),!0}c(de,"stopDaemon");async function V(r){let e=await ve(r),t=e!==null&&W(e),n=null;if(t&&e!==null)try{let{readFile:o}=await import("fs/promises"),i=await o(`/proc/${e}/stat`,"utf8"),s=parseInt(i.split(" ")[21]??"0",10),a=await o("/proc/uptime","utf8"),d=parseFloat(a.split(" ")[0]??"0")-s/100;n=Math.round(d*1e3)}catch{}return{agentId:r,running:t,pid:t?e:null,uptimeMs:n,logFile:H(r)}}c(V,"getDaemonStatus");function No(r){return r.replace(/\/+$/,"").replace(/^ws/,"http")}c(No,"toHttpBase");var y="relay:daemon",jt=["aitable"],Go="dws";function Ho(r,e=jt){if(!Array.isArray(r)||r.length===0)return{ok:!1,reason:"dws args must be a non-empty array"};for(let n of r){if(typeof n!="string")return{ok:!1,reason:"dws args must all be strings"};if(n.length===0)return{ok:!1,reason:"dws args must not contain empty tokens"};if(/[\u0000-\u001f]/.test(n))return{ok:!1,reason:"dws args must not contain control characters"}}let t=r[0];return t.startsWith("-")?{ok:!1,reason:"first dws arg must be a subcommand, not a flag"}:e.length>0&&!e.includes(t)?{ok:!1,reason:`dws command "${t}" is not allowed`}:{ok:!0}}c(Ho,"validateDwsArgs");function qo(r){return Wo(r,["--version"],{timeoutMs:5e3}).ok}c(qo,"detectDwsAvailable");var $e=class{static{c(this,"RelayDaemon")}config;allowedDwsCommands;conn;runAgent;createDwsClient;detectAgents;detectDws;hostname;osString;onPermanentReject;artifactUploader;grantTimeoutMs;pendingGrants=new Map;maxTasks;inflight=0;taskQueue=[];runnerLoop;machineId;constructor(e,t={}){this.config={...e,dwsBin:e.dwsBin??Go},this.machineId=e.machineId,this.allowedDwsCommands=e.allowedDwsCommands??jt,this.runAgent=t.runAgent??Fo,this.createDwsClient=t.createDwsClient??((o,i)=>new Bo({bin:o,timeoutMs:i})),this.detectAgents=t.detectAgents??(()=>Mo()),this.detectDws=t.detectDws??(o=>qo(o)),this.hostname=t.hostname??(()=>F.hostname()),this.osString=t.osString??(()=>`${F.platform()} ${F.arch()}`),this.onPermanentReject=t.onPermanentReject,this.artifactUploader=t.artifactUploader??yt,this.grantTimeoutMs=t.grantTimeoutMs??3e4,this.maxTasks=Math.max(1,Math.floor(e.maxTasks??1));let n=t.createConnection??(o=>new ne(o));this.conn=n({serverUrl:this.config.serverUrl,key:this.config.key,token:this.config.token,onMessage:c(o=>this.handleMessage(o),"onMessage"),onOpen:c(()=>this.handleOpen(),"onOpen"),onPermanentReject:c(o=>this.handlePermanentReject(o),"onPermanentReject")}),this.runnerLoop=this.buildRunnerLoop(t)}buildRunnerLoop(e){let t=Math.max(0,Math.floor(this.config.maxWorkflows??3));if(t===0)return null;let n=this.config.key??this.config.token;if(!n)return w.warn(y,"\u65E0 machine key/owner-token\uFF0C\u8DF3\u8FC7 RunnerLoop\uFF08run \u9636\u6BB5\u4E0D\u8BA4\u9886\u9879\u76EE\uFF09"),null;let o={gatewayUrl:No(this.config.serverUrl),machineKey:n,workspaceRoot:this.config.workspaceRoot,cliPath:Te(),maxWorkflows:t,sendRunEvents:c((i,s)=>this.sendRunEvents(i,s),"sendRunEvents")};return(e.createRunnerLoop??(i=>new me(i)))(o)}sendRunEvents(e,t){t.length!==0&&this.conn.send({type:"run:events",projectId:e,entries:t})}start(){w.info(y,"starting",{serverUrl:this.config.serverUrl,workspaceRoot:this.config.workspaceRoot}),this.config.name||w.warn(y,"\u672A\u6307\u5B9A --name\uFF0C\u5C06\u4F7F\u7528\u4E3B\u673A\u540D\u4F5C\u4E3A\u673A\u5668\u6CE8\u518C\u540D\uFF1B\u591A\u673A\u573A\u666F\u4E0B hostname \u82E5\u91CD\u590D\u4F1A\u5BFC\u81F4\u8FDE\u63A5\u4E92\u76F8\u9876\u66FF\uFF0C\u5EFA\u8BAE\u663E\u5F0F\u6307\u5B9A --name",{hostname:this.hostname()}),this.conn.connect(),this.runnerLoop?.start()}stop(){return w.info(y,"stopping"),this.conn.close(),this.runnerLoop?this.runnerLoop.stop().catch(e=>{w.warn(y,"runner loop \u505C\u6B62\u5F02\u5E38",{error:e instanceof Error?e.message:String(e)})}):Promise.resolve()}buildReady(){let t={type:"ready",runtimes:Array.from(new Set(this.detectAgents().map(n=>n.type))),hasDws:this.detectDws(this.config.dwsBin),hostname:this.hostname(),os:this.osString()};return this.machineId&&(t.machineId=this.machineId),this.config.name&&(t.name=this.config.name),this.config.displayName&&(t.displayName=this.config.displayName),this.config.description&&(t.description=this.config.description),this.maxTasks>1&&(t.maxTasks=this.maxTasks),t}handleOpen(){let e=this.buildReady();w.info(y,"connected; sending ready",{runtimes:e.runtimes,hasDws:e.hasDws}),this.conn.send(e)}handlePermanentReject(e){w.error(y,"permanently rejected by gateway",{...e}),this.onPermanentReject?.(e)}handleMessage(e){switch(e.type){case"run:agent":this.enqueueTask(()=>this.handleRunAgent(e));break;case"run:dws":this.enqueueTask(()=>this.handleRunDws(e));break;case"upload:grant":this.resolveGrant(e);break;case"wf:available":this.runnerLoop?.onWakeup();break;case"ready:ack":e.machineId&&(this.machineId=e.machineId,w.info(y,"machine id assigned",{machineId:e.machineId}));break;default:break}}enqueueTask(e){let t=c(()=>{this.inflight+=1,e().finally(()=>{this.inflight-=1;let n=this.taskQueue.shift();n&&n()})},"exec");this.inflight<this.maxTasks?t():this.taskQueue.push(t)}async handleRunAgent(e){let{requestId:t}=e;w.info(y,"run:agent received",{requestId:t,projectId:e.projectId,agent:e.agent});try{let n=this.resolveWorkDir(e.projectId,e.workDir);await _o(n,{recursive:!0});let o=this.rewritePromptWorkDir(e.prompt,e.workDir,n),i={agent:e.agent,workDir:n,onEvent:c(l=>this.conn.send({type:"event",requestId:t,event:l}),"onEvent")};e.sessionId&&(i.sessionId=e.sessionId),e.permissionMode&&(i.adapterOptions={permissionMode:e.permissionMode}),e.timeoutMs!==void 0?i.timeoutMs=e.timeoutMs:this.config.agentTimeoutMs!=null&&(i.timeoutMs=this.config.agentTimeoutMs);let s=await this.runAgent(o,i),a;try{a=await this.collectAndUploadArtifacts(t,e.projectId,n)}catch(l){let m=l instanceof Error?l.message:String(l);w.error(y,"artifact upload failed",{requestId:t,error:m}),this.conn.send({type:"error",requestId:t,code:"artifact_upload_failed",message:m});return}this.conn.send({type:"result",requestId:t,ok:!0,response:s.response,sessionId:s.sessionId,...a?{artifacts:a}:{}}),w.info(y,"run:agent done",{requestId:t,sessionId:s.sessionId,artifacts:a?a.files.length:0})}catch(n){let o=n instanceof Error?n.message:String(n);w.error(y,"run:agent failed",{requestId:t,error:o}),this.conn.send({type:"error",requestId:t,code:"agent_failed",message:o})}}async collectAndUploadArtifacts(e,t,n){let o=await vt(n);if(o.length===0)return;this.conn.send({type:"upload:request",requestId:e,projectId:t,files:o.map(a=>({path:a.relPath,size:a.size,sha256:a.sha256}))});let i=await this.awaitGrant(e);if(!i)throw new Error("upload:grant \u7B49\u5F85\u8D85\u65F6");if(i.grants.length===0){w.warn(y,"upload:grant \u65E0\u6388\u6743\u9879\uFF08gateway \u672A\u914D\u7F6E store \u6216\u5168\u88AB\u62D2\uFF09",{requestId:e,rejected:(i.rejected??[]).length});return}let s=[];for(let a of i.grants){let l=o.find(d=>d.relPath===a.path);if(!l)continue;let m=await Oo(l.absPath);await this.artifactUploader.upload(a.url,m,a.headers),s.push({path:a.path,key:a.key,sha256:l.sha256,size:l.size})}return{store:"oss",files:s}}awaitGrant(e){return new Promise(t=>{let n=setTimeout(()=>{this.pendingGrants.delete(e),t(void 0)},this.grantTimeoutMs);n.unref?.(),this.pendingGrants.set(e,o=>{clearTimeout(n),t(o)})})}resolveGrant(e){let t=this.pendingGrants.get(e.requestId);t&&(this.pendingGrants.delete(e.requestId),t(e))}async handleRunDws(e){let{requestId:t}=e,n=Ho(e.args,this.allowedDwsCommands);if(!n.ok){w.warn(y,"run:dws rejected",{requestId:t,reason:n.reason}),this.conn.send({type:"error",requestId:t,code:"dws_rejected",message:n.reason});return}w.info(y,"run:dws received",{requestId:t,command:e.args[0]});try{let i=await this.createDwsClient(this.config.dwsBin,e.timeoutMs).runJson(e.args,e.timeoutMs!=null?{timeoutMs:e.timeoutMs}:void 0);this.conn.send({type:"result",requestId:t,ok:!0,response:JSON.stringify(i)}),w.info(y,"run:dws done",{requestId:t})}catch(o){let i=o instanceof Error?o.message:String(o);w.error(y,"run:dws failed",{requestId:t,error:i}),this.conn.send({type:"error",requestId:t,code:"dws_failed",message:i})}}resolveWorkDir(e,t){if(t&&t.trim()!=="")if(M.isAbsolute(t))w.warn(y,"\u5FFD\u7565\u670D\u52A1\u7AEF\u4E0B\u53D1\u7684\u7EDD\u5BF9\u8DEF\u5F84 workDir\uFF0C\u56DE\u9000\u5230 workspaceRoot/projectId",{workDir:t,projectId:e});else return $t(this.config.workspaceRoot,t,{allowRoot:!1});return $t(this.config.workspaceRoot,Pt(e,{label:"projectId"}),{allowRoot:!1})}rewritePromptWorkDir(e,t,n){let o=t?.trim();return!o||!M.isAbsolute(o)||o===n?e:e.split(o).join(n)}};function Pe(r,e){let t,n=r?.trim();if(n)t=M.isAbsolute(n)?M.resolve(n):M.resolve(F.homedir(),n);else{let o=e?.trim()?e.trim():"default";t=M.join(F.homedir(),".aitable-workflow","workspaces",Pt(o,{label:"agentId"}))}return Lo(t,{recursive:!0}),t}c(Pe,"resolveWorkspaceRoot");function Ko(r,e=process.env){let t=r.serverUrl??e.AITABLE_RELAY_SERVER_URL,n=r.key??e.AITABLE_RELAY_BOOTSTRAP_KEY,o=r.token??e.AITABLE_RELAY_TOKEN,i=r.workspaceRoot??e.AITABLE_RELAY_WORKSPACE_ROOT,s=r.machineId??e.AITABLE_RELAY_MACHINE_ID,a=r.name??e.AITABLE_RELAY_NAME,l=r.desc??e.AITABLE_RELAY_DESC,m=r.maxTasks??e.AITABLE_RELAY_MAX_TASKS,d=r.maxWorkflows??e.AITABLE_RELAY_MAX_WORKFLOWS,g=[];if(t||g.push("--server-url (\u6216 AITABLE_RELAY_SERVER_URL)"),!n&&!o&&g.push("--key/--token (\u6216 AITABLE_RELAY_BOOTSTRAP_KEY/AITABLE_RELAY_TOKEN)"),g.length>0)throw new Error(`relay \u7F3A\u5C11\u5FC5\u8981\u53C2\u6570: ${g.join(", ")}
|
|
26
|
-
\u{1F4A1} \u63D0\u793A: \u4F7F\u7528 \`aitable-workflow connect\` \u6CE8\u518C\u540E\u53EF\u7701\u7565\u8FD9\u4E9B\u53C2\u6570`);let u=a??F.hostname()??"default",
|
|
27
|
-
\u{1F4A1} ${je}`);if(r.includes("/_npx/")||r.includes(`${
|
|
28
|
-
\u{1F4A1} ${je}`);if(!
|
|
29
|
-
\u{1F4A1} ${je}`)}c(Lt,"assertStableCliEntry");async function
|
|
30
|
-
`),
|
|
24
|
+
${i}`:""}`}c(fo,"mergeEnvContent");function ho(r){return r===""||/[\s#'"`\\]/.test(r)?`"${r.replace(/\\/g,"\\\\").replace(/"/g,'\\"').replace(/\n/g,"\\n")}"`:r}c(ho,"quoteEnvValue");async function Rt(r,e){let t=await mo(r,"utf8").catch(()=>""),n=fo(t,e);if(n===t||n===""&&t==="")return!1;let o=`${r}.aitable-sync-tmp`;return await uo(o,n,{encoding:"utf8",mode:384}),await po(o,384).catch(()=>{}),await go(o,r),!0}c(Rt,"mergeEnvFile");var Io="workspace-sync",ce=".workspace-meta.json";function Ao(r){return wo("sha256").update(r).digest("hex")}c(Ao,"sha256Hex");async function Ie(r,e){let n=await(r.fetchImpl??fetch)(`${r.gatewayUrl.replace(/\/+$/,"")}${e}`,{headers:{authorization:`Bearer ${r.machineKey}`}});if(!n.ok){let o=await n.text().catch(()=>"");throw new Error(`runner API ${e} \u5931\u8D25: HTTP ${n.status} ${o.slice(0,200)}`)}return n}c(Ie,"apiGet");async function So(r){try{let e=await yo(ae.join(r,ce),"utf8"),t=JSON.parse(e);return t&&typeof t.projectId=="string"?t:null}catch{return null}}c(So,"readWorkspaceMeta");function Se(r,e){return Ro(r,e,{allowRoot:!1})}c(Se,"resolveTracked");async function Eo(r,e){let t=Se(r,e.path);await Ae(ae.dirname(t),{recursive:!0});let n=`${t}.aitable-sync-tmp`;await It(n,e.content,"utf8"),await vo(n,t)}c(Eo,"writeTracked");async function At(r,e,t){let n=await So(t);if(n&&n.projectId!==e)throw new Error(`\u5DE5\u4F5C\u533A\u76EE\u5F55\u5F52\u5C5E\u4E0D\u7B26: \u76EE\u5F55\u8BB0\u5F55 projectId=${n.projectId}\uFF0C\u8BF7\u6C42\u540C\u6B65 ${e}\uFF08${t}\uFF09`);let i=await(await Ie(r,`/api/runner/workspace/${e}`)).json(),s=0,a=0,l="up-to-date",p=n?.trackedManifest??{};if(!n||n.revision!==i.revision){l="synced";let v=await Ie(r,`/api/runner/workspace/${e}/bundle`),I=bo(Buffer.from(await v.arrayBuffer()));if(I.projectId!==e)throw new Error(`\u5FEB\u7167\u5305\u5F52\u5C5E\u4E0D\u7B26: \u5305\u5185 projectId=${I.projectId}\uFF0C\u671F\u671B ${e}`);I.revision!==i.revision&&(i.revision=I.revision),await Ae(t,{recursive:!0});let E={};for(let C of I.files)E[C.path]=Ao(C.content);for(let C of I.files)Se(t,C.path);let z=n?.trackedManifest??{};for(let C of I.files)z[C.path]!==E[C.path]&&(await Eo(t,C),s+=1);for(let C of Object.keys(z))if(E[C]===void 0){let Ne;try{Ne=Se(t,C)}catch(ye){Co.warn(Io,"\u8DF3\u8FC7\u8D8A\u754C\u7684\u65E7 manifest \u6761\u76EE",{projectId:e,path:C,error:ye instanceof Error?ye.message:String(ye)});continue}await ko(Ne,{force:!0}).catch(()=>{}),a+=1}p=E}let g=await(await Ie(r,`/api/runner/workspace/${e}/credentials`)).json();await Ae(t,{recursive:!0});let u=await Rt(ae.join(t,".env"),g.credentials??{}),m={projectId:e,revision:i.revision,syncedAt:new Date().toISOString(),trackedManifest:p};return await It(ae.join(t,ce),`${JSON.stringify(m,null,2)}
|
|
25
|
+
`,"utf8"),{projectId:e,revision:i.revision,action:l,written:s,deleted:a,envUpdated:u}}c(At,"syncWorkspace");var G="runner-gc";async function Et(r){let e=0,t;try{t=await xe(r,{withFileTypes:!0})}catch{return 0}for(let n of t){let o=L.join(r,n.name);if(!n.isSymbolicLink()){if(n.isDirectory()){e+=await Et(o);continue}e+=await le(o).then(i=>i.size).catch(()=>0)}}return e}c(Et,"dirSize");async function xo(r){let e=L.join(r,"projects"),t;try{t=await xe(e,{withFileTypes:!0})}catch{return[]}let n=[];for(let o of t){if(!o.isDirectory())continue;let i=L.join(e,o.name),s=await le(L.join(i,ce)).then(l=>l.mtimeMs).catch(()=>null),a=await le(i).then(l=>l.mtimeMs).catch(()=>0);n.push({projectId:o.name,dir:i,lastTouchedMs:s??a,sizeBytes:await Et(i)})}return n}c(xo,"scanProjects");async function St(r,e){let t=L.join(r,"traces"),n;try{n=await xe(t,{withFileTypes:!0})}catch{return 0}let o=[];for(let a of n){if(!a.isFile())continue;let l=L.join(t,a.name),p=await le(l).catch(()=>null);p&&o.push({full:l,size:p.size,mtimeMs:p.mtimeMs})}let i=o.reduce((a,l)=>a+l.size,0);if(i<=e)return 0;o.sort((a,l)=>a.mtimeMs-l.mtimeMs);let s=0;for(let a of o){if(i<=e)break;await Ee(a.full,{force:!0}).catch(()=>{}),i-=a.size,s+=a.size}return s}c(St,"pruneTraces");async function xt(r){let e=(r.now??Date.now)(),t=(r.ttlDays??14)*24*60*60*1e3,n=r.tracesMaxBytes??500*1024*1024,o=r.maxDiskBytes??20*1024*1024*1024,i=new Set(r.activeProjectIds),s=await xo(r.workspaceRoot),a=[],l=0;for(let u of s){if(i.has(u.projectId)){l+=await St(u.dir,n);continue}if(e-u.lastTouchedMs>=t){await Ee(u.dir,{recursive:!0,force:!0}).catch(m=>{W.warn(G,"\u9879\u76EE\u76EE\u5F55\u56DE\u6536\u5931\u8D25",{projectId:u.projectId,error:m instanceof Error?m.message:String(m)})}),a.push(u.projectId),W.info(G,"\u56DE\u6536\u8FC7\u671F\u9879\u76EE\u76EE\u5F55",{projectId:u.projectId,idleDays:Math.floor((e-u.lastTouchedMs)/864e5)});continue}l+=await St(u.dir,n)}let p=s.filter(u=>!a.includes(u.projectId)),d=p.reduce((u,m)=>u+m.sizeBytes,0)-l,g=!1;if(d>o){g=!0,W.warn(G,"\u5DE5\u4F5C\u533A\u8D85\u78C1\u76D8\u6C34\u4F4D\uFF0C\u5F00\u59CB\u56DE\u6536\u6700\u4E45\u672A\u8BA4\u9886\u9879\u76EE",{totalBytes:d,maxDiskBytes:o});let u=p.filter(m=>!i.has(m.projectId)).sort((m,v)=>m.lastTouchedMs-v.lastTouchedMs);for(let m of u){if(d<=o)break;await Ee(m.dir,{recursive:!0,force:!0}).catch(()=>{}),a.push(m.projectId),d-=m.sizeBytes,W.warn(G,"\u6C34\u4F4D\u56DE\u6536\u9879\u76EE\u76EE\u5F55",{projectId:m.projectId,freedBytes:m.sizeBytes})}d>o&&W.error(G,"\u56DE\u6536\u540E\u4ECD\u8D85\u78C1\u76D8\u6C34\u4F4D\uFF08\u6D3B\u8DC3\u9879\u76EE\u5360\u7528\uFF09\uFF0C\u9700\u4EBA\u5DE5\u4ECB\u5165",{totalBytes:d,maxDiskBytes:o})}return{removedProjects:a,tracesFreedBytes:l,diskPressure:g,totalBytes:Math.max(0,d)}}c(xt,"runGc");var b="runner-loop",pe=class{static{c(this,"RunnerLoop")}options;maxWorkflows;setTimer;clearTimer;doSync;holdings=new Map;pollTimer=null;gcTimer=null;running=!1;claiming=!1;constructor(e){this.options=e,this.maxWorkflows=Math.max(1,Math.floor(e.maxWorkflows??3)),this.setTimer=e.setTimeoutImpl??((t,n)=>setTimeout(t,n)),this.clearTimer=e.clearTimeoutImpl??(t=>clearTimeout(t)),this.doSync=e.syncImpl??At}get held(){return[...this.holdings.values()].map(e=>({projectId:e.projectId,generation:e.generation,revision:e.revision,engine:e.engine.status}))}get freeSlots(){return Math.max(0,this.maxWorkflows-this.holdings.size)}start(){this.running||(this.running=!0,k.info(b,"runner loop \u542F\u52A8",{maxWorkflows:this.maxWorkflows,workspaceRoot:this.options.workspaceRoot}),this.claimOnce(),this.scheduleGc(0))}async stop(){this.running=!1,this.pollTimer&&(this.clearTimer(this.pollTimer),this.pollTimer=null),this.gcTimer&&(this.clearTimer(this.gcTimer),this.gcTimer=null),await Promise.all([...this.holdings.keys()].map(e=>this.releaseHolding(e,"daemon_shutdown")))}scheduleGc(e){let t=this.options.gcIntervalMs??864e5;!this.running||t<=0||(this.gcTimer&&this.clearTimer(this.gcTimer),this.gcTimer=this.setTimer(()=>{this.gcTimer=null,this.gcOnce().finally(()=>this.scheduleGc(t))},e))}async gcOnce(){try{let e=await(this.options.gcImpl??xt)({workspaceRoot:this.options.workspaceRoot,activeProjectIds:[...this.holdings.keys()],...this.options.gcOptions??{}});(e.removedProjects.length>0||e.tracesFreedBytes>0)&&k.info(b,"\u672C\u673A\u56DE\u6536\u5B8C\u6210",{removed:e.removedProjects.length,tracesFreedBytes:e.tracesFreedBytes,totalBytes:e.totalBytes})}catch(e){k.warn(b,"\u672C\u673A\u56DE\u6536\u5931\u8D25",{error:$(e)})}}onWakeup(){this.running&&this.claimOnce()}async claimOnce(){if(!this.running||this.claiming)return;this.claiming=!0;let e=this.options.defaultPollMs??1e4;try{let t=this.freeSlots;if(t>0){let n=await this.post("/api/runner/workflows/claim",{capacity:t});typeof n.nextPollAfterMs=="number"&&n.nextPollAfterMs>0&&(e=n.nextPollAfterMs);for(let o of n.grants??[])await this.acceptGrant(o,n.heartbeatIntervalSeconds)}}catch(t){k.warn(b,"claim \u5931\u8D25\uFF08\u9000\u907F\u540E\u91CD\u8BD5\uFF09",{error:$(t)}),e=Math.max(e,5e3)}finally{this.claiming=!1,this.scheduleNextPoll(e)}}scheduleNextPoll(e){this.running&&(this.pollTimer&&this.clearTimer(this.pollTimer),this.pollTimer=this.setTimer(()=>{this.pollTimer=null,this.claimOnce()},e))}async acceptGrant(e,t){if(this.holdings.has(e.projectId))return;let n=Tt.join(this.options.workspaceRoot,"projects",e.projectId);k.info(b,"\u8BA4\u9886\u9879\u76EE",{projectId:e.projectId,generation:e.generation,workspaceDir:n});let o;try{o=await this.doSync(this.clientConfig(),e.projectId,n)}catch(l){k.error(b,"\u5DE5\u4F5C\u533A\u540C\u6B65\u5931\u8D25\uFF0C\u4EA4\u8FD8\u79DF\u7EA6",{projectId:e.projectId,error:$(l)}),await this.postSafe(`/api/runner/workflows/${e.projectId}/release`,{generation:e.generation,reason:"workspace_sync_failed"});return}let i=(this.options.createSupervisor??To)({projectId:e.projectId,workspaceDir:n,cliPath:this.options.cliPath,onRunEvent:c(l=>this.handleRunEvent(e.projectId,l),"onRunEvent"),onCrashLoop:c(()=>{this.releaseHolding(e.projectId,"engine_crash_loop")},"onCrashLoop")}),s=this.options.sendRunEvents?new se({projectId:e.projectId,spoolDir:n,send:c(l=>this.options.sendRunEvents(e.projectId,l),"send")}):null;s?.start();let a={projectId:e.projectId,generation:e.generation,revision:o.revision,engine:i,spool:s,heartbeatTimer:null,busy:!1};this.holdings.set(e.projectId,a),i.start(),this.scheduleHeartbeat(a,(t??15)*1e3)}handleRunEvent(e,t){this.holdings.get(e)?.spool?.append(t),this.options.onRunEvent?.(e,t)}scheduleHeartbeat(e,t){this.running&&(e.heartbeatTimer&&this.clearTimer(e.heartbeatTimer),e.heartbeatTimer=this.setTimer(()=>{e.heartbeatTimer=null,this.heartbeatOnce(e)},t))}async heartbeatOnce(e){if(!this.running||e.busy||!this.holdings.has(e.projectId))return;let t=this.options.defaultHeartbeatMs??15e3;try{let n=await this.post(`/api/runner/workflows/${e.projectId}/heartbeat`,{generation:e.generation,workspaceRevision:e.revision,engineStatus:e.engine.status,...e.spool?{lastEventSeq:e.spool.lastSeq}:{}}),o=typeof n.nextIntervalSeconds=="number"&&n.nextIntervalSeconds>0?n.nextIntervalSeconds*1e3:t;if(e.spool&&typeof n.lastEventSeq=="number"&&e.spool.replayFrom(n.lastEventSeq),n.action==="stop"){k.info(b,"\u5FC3\u8DF3\u4E0B\u53D1 stop\uFF0C\u505C\u6B62\u5F15\u64CE",{projectId:e.projectId}),await this.releaseHolding(e.projectId,"heartbeat_stop");return}if(n.action==="resync"){await this.resyncHolding(e)&&this.scheduleHeartbeat(e,o);return}e.engine.markHealthy(),this.scheduleHeartbeat(e,o)}catch(n){k.warn(b,"\u5FC3\u8DF3\u5931\u8D25\uFF08\u4FDD\u6301\u5F15\u64CE\u8FD0\u884C\uFF0C\u7B49\u5F85\u6062\u590D\u6216\u79DF\u7EA6\u8FC7\u671F\uFF09",{projectId:e.projectId,error:$(n)}),this.scheduleHeartbeat(e,Math.min(t,5e3))}}async resyncHolding(e){if(e.busy)return!0;e.busy=!0;let t=Tt.join(this.options.workspaceRoot,"projects",e.projectId);try{k.info(b,"\u5DE5\u4F5C\u533A\u9700\u91CD\u540C\u6B65\uFF0C\u91CD\u542F\u5F15\u64CE",{projectId:e.projectId}),await e.engine.stop();let n=await this.doSync(this.clientConfig(),e.projectId,t);e.revision=n.revision,e.engine.start()}catch(n){return k.error(b,"\u91CD\u540C\u6B65\u5931\u8D25\uFF0C\u4EA4\u8FD8\u79DF\u7EA6",{projectId:e.projectId,error:$(n)}),e.busy=!1,await this.releaseHolding(e.projectId,"resync_failed"),!1}return e.busy=!1,!0}async releaseHolding(e,t){let n=this.holdings.get(e);n&&(this.holdings.delete(e),n.heartbeatTimer&&(this.clearTimer(n.heartbeatTimer),n.heartbeatTimer=null),await n.engine.stop().catch(o=>{k.warn(b,"\u505C\u6B62\u5F15\u64CE\u5F02\u5E38",{projectId:e,error:$(o)})}),await n.spool?.stop().catch(()=>{}),await this.postSafe(`/api/runner/workflows/${e}/release`,{generation:n.generation,reason:t}),k.info(b,"\u5DF2\u8BA9\u51FA\u79DF\u7EA6",{projectId:e,reason:t}),this.running&&this.scheduleNextPoll(this.options.defaultPollMs??1e4))}clientConfig(){return{gatewayUrl:this.options.gatewayUrl,machineKey:this.options.machineKey,...this.options.fetchImpl?{fetchImpl:this.options.fetchImpl}:{}}}async post(e,t){let o=await(this.options.fetchImpl??fetch)(`${this.options.gatewayUrl.replace(/\/+$/,"")}${e}`,{method:"POST",headers:{authorization:`Bearer ${this.options.machineKey}`,"content-type":"application/json"},body:JSON.stringify(t??{})});if(!o.ok){let i=await o.text().catch(()=>"");throw new Error(`runner API ${e} \u5931\u8D25: HTTP ${o.status} ${i.slice(0,200)}`)}return await o.json()}async postSafe(e,t){try{await this.post(e,t)}catch(n){k.warn(b,"\u6536\u5C3E\u8BF7\u6C42\u5931\u8D25\uFF08\u79DF\u7EA6\u5C06\u7531\u670D\u52A1\u7AEF\u8FC7\u671F\u56DE\u6536\uFF09",{pathname:e,error:$(n)})}}};function To(r){return new oe({projectId:r.projectId,workspaceDir:r.workspaceDir,cliPath:r.cliPath,...r.onRunEvent?{onRunEvent:r.onRunEvent}:{},...r.onCrashLoop?{onCrashLoop:c(()=>r.onCrashLoop(),"onCrashLoop")}:{}})}c(To,"defaultSupervisorFactory");function $(r){return r instanceof Error?r.message:String(r)}c($,"describeError");import{spawn as Po}from"child_process";import{existsSync as jo}from"fs";import q from"path";import{fileURLToPath as Oo}from"url";import{appendFileSync as js,statSync as Os,renameSync as Ds,mkdirSync as _s,existsSync as Ls}from"fs";import $o from"path";var Bs=10*1024*1024;function H(r){return $o.join(X(r),"daemon.log")}c(H,"logFilePath");var me=q.dirname(Oo(import.meta.url));function Te(){let r=[q.resolve(me,"..","..","bin","aitable-workflow.mjs"),q.resolve(me,"..","bin","aitable-workflow.mjs"),q.resolve(me,"..","..","dist","cli.js"),q.resolve(me,"cli.js")];return r.find(e=>jo(e))??r[0]}c(Te,"resolveDaemonEntry");var Do=Te();async function K(r){await qe(r.agentId);let e=["relay"];r.serverUrl&&e.push("--server-url",r.serverUrl),r.token&&e.push("--token",r.token),r.key&&e.push("--key",r.key),r.name&&e.push("--name",r.name),r.workspaceRoot&&e.push("--workspace-root",r.workspaceRoot),r.machineId&&e.push("--machine-id",r.machineId),r.maxTasks&&e.push("--max-tasks",String(r.maxTasks));let t=await import("fs").then(s=>{let a=H(r.agentId);return s.openSync(a,"a")}),n=Po(process.execPath,[Do,...e],{detached:!0,stdio:["ignore",t,t],env:{...process.env}});try{let{closeSync:s}=await import("fs");s(t)}catch{}n.unref();let{writeFile:o}=await import("fs/promises"),{pidFilePath:i}=await import("./pid-file-KT5KHCKU.js");return n.pid&&await o(i(r.agentId),String(n.pid),"utf8"),{pid:n.pid??0}}c(K,"startDaemon");async function de(r,e=5e3){let t=await ve(r);if(t===null||!N(t))return await Q(r),!1;try{process.kill(t,"SIGTERM")}catch{return await Q(r),!1}let n=Date.now()+e;for(;Date.now()<n&&N(t);)await new Promise(o=>setTimeout(o,200));if(N(t))try{process.kill(t,"SIGKILL")}catch{}return await Q(r),!0}c(de,"stopDaemon");async function V(r){let e=await ve(r),t=e!==null&&N(e),n=null;if(t&&e!==null)try{let{readFile:o}=await import("fs/promises"),i=await o(`/proc/${e}/stat`,"utf8"),s=parseInt(i.split(" ")[21]??"0",10),a=await o("/proc/uptime","utf8"),d=parseFloat(a.split(" ")[0]??"0")-s/100;n=Math.round(d*1e3)}catch{}return{agentId:r,running:t,pid:t?e:null,uptimeMs:n,logFile:H(r)}}c(V,"getDaemonStatus");function Go(r){return r.replace(/\/+$/,"").replace(/^ws/,"http")}c(Go,"toHttpBase");var y="relay:daemon",jt=["aitable"],Ho="dws";function qo(r,e=jt){if(!Array.isArray(r)||r.length===0)return{ok:!1,reason:"dws args must be a non-empty array"};for(let n of r){if(typeof n!="string")return{ok:!1,reason:"dws args must all be strings"};if(n.length===0)return{ok:!1,reason:"dws args must not contain empty tokens"};if(/[\u0000-\u001f]/.test(n))return{ok:!1,reason:"dws args must not contain control characters"}}let t=r[0];return t.startsWith("-")?{ok:!1,reason:"first dws arg must be a subcommand, not a flag"}:e.length>0&&!e.includes(t)?{ok:!1,reason:`dws command "${t}" is not allowed`}:{ok:!0}}c(qo,"validateDwsArgs");function Ko(r){return Wo(r,["--version"],{timeoutMs:5e3}).ok}c(Ko,"detectDwsAvailable");var $e=class{static{c(this,"RelayDaemon")}config;allowedDwsCommands;conn;runAgent;createDwsClient;detectAgents;detectDws;hostname;osString;onPermanentReject;artifactUploader;grantTimeoutMs;pendingGrants=new Map;maxTasks;inflight=0;taskQueue=[];runnerLoop;machineId;constructor(e,t={}){this.config={...e,dwsBin:e.dwsBin??Ho},this.machineId=e.machineId,this.allowedDwsCommands=e.allowedDwsCommands??jt,this.runAgent=t.runAgent??Bo,this.createDwsClient=t.createDwsClient??((o,i)=>new Uo({bin:o,timeoutMs:i})),this.detectAgents=t.detectAgents??(()=>Fo()),this.detectDws=t.detectDws??(o=>Ko(o)),this.hostname=t.hostname??(()=>F.hostname()),this.osString=t.osString??(()=>`${F.platform()} ${F.arch()}`),this.onPermanentReject=t.onPermanentReject,this.artifactUploader=t.artifactUploader??yt,this.grantTimeoutMs=t.grantTimeoutMs??3e4,this.maxTasks=Math.max(1,Math.floor(e.maxTasks??1));let n=t.createConnection??(o=>new ne(o));this.conn=n({serverUrl:this.config.serverUrl,key:this.config.key,token:this.config.token,onMessage:c(o=>this.handleMessage(o),"onMessage"),onOpen:c(()=>this.handleOpen(),"onOpen"),onPermanentReject:c(o=>this.handlePermanentReject(o),"onPermanentReject")}),this.runnerLoop=this.buildRunnerLoop(t)}buildRunnerLoop(e){let t=Math.max(0,Math.floor(this.config.maxWorkflows??3));if(t===0)return null;let n=this.config.key??this.config.token;if(!n)return w.warn(y,"\u65E0 machine key/owner-token\uFF0C\u8DF3\u8FC7 RunnerLoop\uFF08run \u9636\u6BB5\u4E0D\u8BA4\u9886\u9879\u76EE\uFF09"),null;let o={gatewayUrl:Go(this.config.serverUrl),machineKey:n,workspaceRoot:this.config.workspaceRoot,cliPath:Te(),maxWorkflows:t,sendRunEvents:c((i,s)=>this.sendRunEvents(i,s),"sendRunEvents")};return(e.createRunnerLoop??(i=>new pe(i)))(o)}sendRunEvents(e,t){t.length!==0&&this.conn.send({type:"run:events",projectId:e,entries:t})}start(){w.info(y,"starting",{serverUrl:this.config.serverUrl,workspaceRoot:this.config.workspaceRoot}),this.config.name||w.warn(y,"\u672A\u6307\u5B9A --name\uFF0C\u5C06\u4F7F\u7528\u4E3B\u673A\u540D\u4F5C\u4E3A\u673A\u5668\u6CE8\u518C\u540D\uFF1B\u591A\u673A\u573A\u666F\u4E0B hostname \u82E5\u91CD\u590D\u4F1A\u5BFC\u81F4\u8FDE\u63A5\u4E92\u76F8\u9876\u66FF\uFF0C\u5EFA\u8BAE\u663E\u5F0F\u6307\u5B9A --name",{hostname:this.hostname()}),this.conn.connect(),this.runnerLoop?.start()}stop(){return w.info(y,"stopping"),this.conn.close(),this.runnerLoop?this.runnerLoop.stop().catch(e=>{w.warn(y,"runner loop \u505C\u6B62\u5F02\u5E38",{error:e instanceof Error?e.message:String(e)})}):Promise.resolve()}buildReady(){let t={type:"ready",runtimes:Array.from(new Set(this.detectAgents().map(n=>n.type))),hasDws:this.detectDws(this.config.dwsBin),hostname:this.hostname(),os:this.osString()};return this.machineId&&(t.machineId=this.machineId),this.config.name&&(t.name=this.config.name),this.config.displayName&&(t.displayName=this.config.displayName),this.config.description&&(t.description=this.config.description),this.maxTasks>1&&(t.maxTasks=this.maxTasks),t}handleOpen(){let e=this.buildReady();w.info(y,"connected; sending ready",{runtimes:e.runtimes,hasDws:e.hasDws}),this.conn.send(e)}handlePermanentReject(e){w.error(y,"permanently rejected by gateway",{...e}),this.onPermanentReject?.(e)}handleMessage(e){switch(e.type){case"run:agent":this.enqueueTask(()=>this.handleRunAgent(e));break;case"run:dws":this.enqueueTask(()=>this.handleRunDws(e));break;case"upload:grant":this.resolveGrant(e);break;case"wf:available":this.runnerLoop?.onWakeup();break;case"ready:ack":e.machineId&&(this.machineId=e.machineId,w.info(y,"machine id assigned",{machineId:e.machineId}));break;default:break}}enqueueTask(e){let t=c(()=>{this.inflight+=1,e().finally(()=>{this.inflight-=1;let n=this.taskQueue.shift();n&&n()})},"exec");this.inflight<this.maxTasks?t():this.taskQueue.push(t)}async handleRunAgent(e){let{requestId:t}=e;w.info(y,"run:agent received",{requestId:t,projectId:e.projectId,agent:e.agent});try{let n=this.resolveWorkDir(e.projectId,e.workDir);await _o(n,{recursive:!0});let o=this.rewritePromptWorkDir(e.prompt,e.workDir,n),i={agent:e.agent,workDir:n,onEvent:c(l=>this.conn.send({type:"event",requestId:t,event:l}),"onEvent")};e.sessionId&&(i.sessionId=e.sessionId),e.permissionMode&&(i.adapterOptions={permissionMode:e.permissionMode}),e.timeoutMs!==void 0?i.timeoutMs=e.timeoutMs:this.config.agentTimeoutMs!=null&&(i.timeoutMs=this.config.agentTimeoutMs);let s=await this.runAgent(o,i),a;try{a=await this.collectAndUploadArtifacts(t,e.projectId,n)}catch(l){let p=l instanceof Error?l.message:String(l);w.error(y,"artifact upload failed",{requestId:t,error:p}),this.conn.send({type:"error",requestId:t,code:"artifact_upload_failed",message:p});return}this.conn.send({type:"result",requestId:t,ok:!0,response:s.response,sessionId:s.sessionId,...a?{artifacts:a}:{}}),w.info(y,"run:agent done",{requestId:t,sessionId:s.sessionId,artifacts:a?a.files.length:0})}catch(n){let o=n instanceof Error?n.message:String(n);w.error(y,"run:agent failed",{requestId:t,error:o}),this.conn.send({type:"error",requestId:t,code:"agent_failed",message:o})}}async collectAndUploadArtifacts(e,t,n){let o=await vt(n);if(o.length===0)return;this.conn.send({type:"upload:request",requestId:e,projectId:t,files:o.map(a=>({path:a.relPath,size:a.size,sha256:a.sha256}))});let i=await this.awaitGrant(e);if(!i)throw new Error("upload:grant \u7B49\u5F85\u8D85\u65F6");if(i.grants.length===0){w.warn(y,"upload:grant \u65E0\u6388\u6743\u9879\uFF08gateway \u672A\u914D\u7F6E store \u6216\u5168\u88AB\u62D2\uFF09",{requestId:e,rejected:(i.rejected??[]).length});return}let s=[];for(let a of i.grants){let l=o.find(d=>d.relPath===a.path);if(!l)continue;let p=await Lo(l.absPath);await this.artifactUploader.upload(a.url,p,a.headers),s.push({path:a.path,key:a.key,sha256:l.sha256,size:l.size})}return{store:"oss",files:s}}awaitGrant(e){return new Promise(t=>{let n=setTimeout(()=>{this.pendingGrants.delete(e),t(void 0)},this.grantTimeoutMs);n.unref?.(),this.pendingGrants.set(e,o=>{clearTimeout(n),t(o)})})}resolveGrant(e){let t=this.pendingGrants.get(e.requestId);t&&(this.pendingGrants.delete(e.requestId),t(e))}async handleRunDws(e){let{requestId:t}=e,n=qo(e.args,this.allowedDwsCommands);if(!n.ok){w.warn(y,"run:dws rejected",{requestId:t,reason:n.reason}),this.conn.send({type:"error",requestId:t,code:"dws_rejected",message:n.reason});return}w.info(y,"run:dws received",{requestId:t,command:e.args[0]});try{let i=await this.createDwsClient(this.config.dwsBin,e.timeoutMs).runJson(e.args,e.timeoutMs!=null?{timeoutMs:e.timeoutMs}:void 0);this.conn.send({type:"result",requestId:t,ok:!0,response:JSON.stringify(i)}),w.info(y,"run:dws done",{requestId:t})}catch(o){let i=o instanceof Error?o.message:String(o);w.error(y,"run:dws failed",{requestId:t,error:i}),this.conn.send({type:"error",requestId:t,code:"dws_failed",message:i})}}resolveWorkDir(e,t){if(t&&t.trim()!=="")if(M.isAbsolute(t))w.warn(y,"\u5FFD\u7565\u670D\u52A1\u7AEF\u4E0B\u53D1\u7684\u7EDD\u5BF9\u8DEF\u5F84 workDir\uFF0C\u56DE\u9000\u5230 workspaceRoot/projectId",{workDir:t,projectId:e});else return $t(this.config.workspaceRoot,t,{allowRoot:!1});return $t(this.config.workspaceRoot,Pt(e,{label:"projectId"}),{allowRoot:!1})}rewritePromptWorkDir(e,t,n){let o=t?.trim();return!o||!M.isAbsolute(o)||o===n?e:e.split(o).join(n)}};function Pe(r,e){let t,n=r?.trim();if(n)t=M.isAbsolute(n)?M.resolve(n):M.resolve(F.homedir(),n);else{let o=e?.trim()?e.trim():"default";t=M.join(F.homedir(),".aitable-workflow","workspaces",Pt(o,{label:"agentId"}))}return Mo(t,{recursive:!0}),t}c(Pe,"resolveWorkspaceRoot");function Vo(r,e=process.env){let t=r.serverUrl??e.AITABLE_RELAY_SERVER_URL,n=r.key??e.AITABLE_RELAY_BOOTSTRAP_KEY,o=r.token??e.AITABLE_RELAY_TOKEN,i=r.workspaceRoot??e.AITABLE_RELAY_WORKSPACE_ROOT,s=r.machineId??e.AITABLE_RELAY_MACHINE_ID,a=r.name??e.AITABLE_RELAY_NAME,l=r.desc??e.AITABLE_RELAY_DESC,p=r.maxTasks??e.AITABLE_RELAY_MAX_TASKS,d=r.maxWorkflows??e.AITABLE_RELAY_MAX_WORKFLOWS,g=[];if(t||g.push("--server-url (\u6216 AITABLE_RELAY_SERVER_URL)"),!n&&!o&&g.push("--key/--token (\u6216 AITABLE_RELAY_BOOTSTRAP_KEY/AITABLE_RELAY_TOKEN)"),g.length>0)throw new Error(`relay \u7F3A\u5C11\u5FC5\u8981\u53C2\u6570: ${g.join(", ")}
|
|
26
|
+
\u{1F4A1} \u63D0\u793A: \u4F7F\u7528 \`aitable-workflow connect\` \u6CE8\u518C\u540E\u53EF\u7701\u7565\u8FD9\u4E9B\u53C2\u6570`);let u=a??F.hostname()??"default",m={serverUrl:t,workspaceRoot:Pe(i,u)};if(o?m.token=o:n&&(m.key=n),s&&(m.machineId=s),a&&(m.name=a,m.displayName=a),l&&(m.description=l),p!=null&&p!==""){let v=Math.floor(Number(p));Number.isFinite(v)&&v>=1&&(m.maxTasks=v)}if(d!=null&&d!==""){let v=Math.floor(Number(d));Number.isFinite(v)&&v>=0&&(m.maxWorkflows=v)}return m}c(Vo,"resolveRelayCliConfig");async function B(r,e=process.env){let t=null;try{t=await No()}catch{t=null}let n={serverUrl:r.serverUrl??e.AITABLE_RELAY_SERVER_URL??t?.serverUrl,key:r.key??e.AITABLE_RELAY_BOOTSTRAP_KEY??t?.key,token:r.token??e.AITABLE_RELAY_TOKEN??t?.token,name:r.name??e.AITABLE_RELAY_NAME??t?.name,desc:r.desc??e.AITABLE_RELAY_DESC,workspaceRoot:r.workspaceRoot??e.AITABLE_RELAY_WORKSPACE_ROOT??t?.workspaceRoot,machineId:r.machineId??e.AITABLE_RELAY_MACHINE_ID??t?.machineId,maxTasks:r.maxTasks??e.AITABLE_RELAY_MAX_TASKS,maxWorkflows:r.maxWorkflows??e.AITABLE_RELAY_MAX_WORKFLOWS};return Vo(n,e)}c(B,"resolveDaemonConfig");async function ge(r){let e=await B(r),t=new $e(e,{onPermanentReject:c(()=>{process.exit(1)},"onPermanentReject")}),n=!1,o=c(()=>{n&&(w.warn(y,"\u518D\u6B21\u6536\u5230\u9000\u51FA\u4FE1\u53F7\uFF0C\u5F3A\u5236\u9000\u51FA"),process.exit(1)),n=!0,t.stop().catch(()=>{}).finally(()=>process.exit(0))},"shutdown");return process.on("SIGINT",o),process.on("SIGTERM",o),t.start(),t}c(ge,"runRelayDaemon");import{readFile as Yo,writeFile as la,mkdir as pa}from"fs/promises";import Jo from"path";var Ot={connectionState:"disconnected",lastHeartbeatAt:null,machineId:null,serverUrl:"",runningTasks:0,queuedTasks:0,lastDisconnectReason:null,updatedAt:new Date().toISOString()};function zo(r){return Jo.join(X(r),"state.json")}c(zo,"stateFilePath");async function ue(r){try{let e=await Yo(zo(r),"utf8");return{...Ot,...JSON.parse(e)}}catch{return{...Ot}}}c(ue,"readState");import{execSync as Y}from"child_process";import{writeFile as Xo,rm as Qo,mkdir as Zo,access as er}from"fs/promises";import S from"path";import Dt from"os";import{assertPortableId as tr,readCredentials as nr}from"aitable-workflow-core";var or="com.alibaba.aitable-workflow.daemon",je="\u8BF7\u5148\u5168\u5C40\u5B89\u88C5\uFF1Anpm i -g @ali/aitable-workflow";function J(r){return`${or}.${tr(r,{label:"agentId"})}`}c(J,"plistLabel");function Oe(r){return S.join(Dt.homedir(),"Library","LaunchAgents",`${J(r)}.plist`)}c(Oe,"plistPath");function P(){return process.platform==="darwin"}c(P,"isMacOS");function _t(){try{return Y("which aitable-workflow",{encoding:"utf8"}).trim()||null}catch{return null}}c(_t,"resolveGlobalCliEntry");function Lt(r){if(!r||!r.trim())throw new Error(`\u672A\u627E\u5230\u5168\u5C40\u5B89\u88C5\u7684 aitable-workflow CLI \u5165\u53E3\uFF0C\u65E0\u6CD5\u914D\u7F6E\u5F00\u673A\u81EA\u542F\u3002
|
|
27
|
+
\u{1F4A1} ${je}`);if(r.includes("/_npx/")||r.includes(`${S.sep}_npx${S.sep}`))throw new Error(`\u68C0\u6D4B\u5230 CLI \u5165\u53E3\u4F4D\u4E8E npx \u4E34\u65F6\u7F13\u5B58\uFF0C\u5F00\u673A\u81EA\u542F\u4F1A\u5931\u6548\uFF1A${r}
|
|
28
|
+
\u{1F4A1} ${je}`);if(!S.isAbsolute(r))throw new Error(`CLI \u5165\u53E3\u4E0D\u662F\u7A33\u5B9A\u7684\u5168\u5C40\u7EDD\u5BF9\u8DEF\u5F84\uFF1A${r}
|
|
29
|
+
\u{1F4A1} ${je}`)}c(Lt,"assertStableCliEntry");async function rr(r){let e={};e.AITABLE_RELAY_NAME=r;let t;try{let n=await nr();t=n.agents.find(o=>o.name===r)??n.agents.find(o=>o.name===n.activeAgent)??void 0}catch{t=void 0}return t&&(t.serverUrl&&(e.AITABLE_RELAY_SERVER_URL=t.serverUrl),t.token&&(e.AITABLE_RELAY_TOKEN=t.token),t.workspaceRoot&&(e.AITABLE_RELAY_WORKSPACE_ROOT=t.workspaceRoot)),e}c(rr,"buildRelayEnv");function ir(r,e){let t=J(r),n=e?.cliEntry??_t();Lt(n);let o=["relay"];e?.maxTasks&&o.push("--max-tasks",String(e.maxTasks));let i=S.join(Dt.homedir(),".aitable-workflow","daemons",r),s=S.join(i,"launchd.log"),a=S.join(i,"launchd.err.log"),l=[n,...o].map(g=>` <string>${U(g)}</string>`).join(`
|
|
30
|
+
`),p={PATH:process.env.PATH??"/usr/local/bin:/usr/bin:/bin",...e?.relayEnv??{}},d=Object.entries(p).map(([g,u])=>` <key>${U(g)}</key>
|
|
31
31
|
<string>${U(u)}</string>`).join(`
|
|
32
32
|
`);return`<?xml version="1.0" encoding="UTF-8"?>
|
|
33
33
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
@@ -53,41 +53,44 @@ ${d}
|
|
|
53
53
|
<string>${U(a)}</string>
|
|
54
54
|
</dict>
|
|
55
55
|
</plist>
|
|
56
|
-
`}c(
|
|
57
|
-
Agent: ${i.agentId}`),console.log(`Running: ${i.running?"\u2705 yes":"\u274C no"}`),i.pid&&console.log(`PID: ${i.pid}`),i.uptimeMs!==null){let a=Math.floor(i.uptimeMs/1e3),l=Math.floor(a/60),
|
|
58
|
-
--- Configuration ---`),console.log(`Server: ${s.serverUrl}`),console.log(`Token: ${
|
|
59
|
-
`);console.log(
|
|
60
|
-
`))}let l=a.length;
|
|
56
|
+
`}c(ir,"generatePlist");function U(r){return r.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}c(U,"escapeXml");function fe(){let r=process.getuid?.();if(r===void 0)throw new Error("\u65E0\u6CD5\u83B7\u53D6\u7528\u6237 UID\uFF08\u4EC5\u652F\u6301 macOS/Linux\uFF09");return r}c(fe,"getUid");async function Mt(r,e){if(!P())throw new Error("\u5F00\u673A\u81EA\u542F\u5F53\u524D\u4EC5\u652F\u6301 macOS");let t=_t();Lt(t);let n=await rr(r),o=Oe(r),i=J(r);try{Y(`launchctl bootout gui/${fe()}/${i} 2>/dev/null`,{encoding:"utf8"})}catch{}await Zo(S.dirname(o),{recursive:!0});let s=ir(r,{maxTasks:e?.maxTasks,relayEnv:n,cliEntry:t});await Xo(o,s,"utf8"),Y(`launchctl bootstrap gui/${fe()} ${o}`,{encoding:"utf8"})}c(Mt,"enableAutostart");async function Ft(r){if(!P())throw new Error("\u5F00\u673A\u81EA\u542F\u5F53\u524D\u4EC5\u652F\u6301 macOS");let e=J(r),t=Oe(r);try{Y(`launchctl bootout gui/${fe()}/${e} 2>/dev/null`,{encoding:"utf8"})}catch{}await Qo(t,{force:!0})}c(Ft,"disableAutostart");async function De(r){if(!P())throw new Error("\u5F00\u673A\u81EA\u542F\u5F53\u524D\u4EC5\u652F\u6301 macOS");let e=J(r),t=Oe(r),n=!1;try{await er(t),n=!0}catch{n=!1}let o=!1,i=!1,s=null;try{let a=Y(`launchctl print gui/${fe()}/${e} 2>/dev/null`,{encoding:"utf8"});o=!0;let l=a.match(/pid\s*=\s*(\d+)/);l&&(s=parseInt(l[1],10),i=!0)}catch{o=!1}return{installed:n,loaded:o,running:i,pid:s}}c(De,"getAutostartStatus");import{watchFile as ar,statSync as cr,openSync as lr,readSync as pr,closeSync as mr}from"fs";import{readFile as Ut}from"fs/promises";function Nt(r,e){let t=r.command("daemon").description("\u540E\u53F0 relay daemon \u751F\u547D\u5468\u671F\u7BA1\u7406");t.command("start").description("\u4EE5\u540E\u53F0\u8FDB\u7A0B\u542F\u52A8 relay daemon").option("--agent <name>","Agent \u540D\u79F0\uFF08\u9ED8\u8BA4\u4F7F\u7528 activeAgent\uFF09").option("--server-url <url>","Gateway \u5730\u5740").option("--token <ownerToken>","owner-token").option("--key <machineKey>","per-machine key\uFF08sk_machine_*\uFF09").option("--workspace-root <dir>","\u5DE5\u4F5C\u533A\u6839\u76EE\u5F55").option("--max-tasks <n>","\u5E76\u53D1\u4E0A\u9650","1").action(async n=>{try{let o=n.agent;if(!o){let a=await j();if(!a)throw new Error("\u672A\u6307\u5B9A --agent \u4E14\u65E0\u6D3B\u8DC3 Agent\u3002\u4F7F\u7528 `aitable-workflow connect` \u6CE8\u518C\u6216 `--agent <name>` \u6307\u5B9A\u3002");o=a.name}let i=await B({serverUrl:n.serverUrl,token:n.token,key:n.key,workspaceRoot:n.workspaceRoot,name:o,maxTasks:n.maxTasks}),s=await K({agentId:o,serverUrl:i.serverUrl,token:i.token,key:i.key,name:i.name??o,workspaceRoot:i.workspaceRoot,machineId:i.machineId,maxTasks:i.maxTasks});console.log("\u2705 Daemon \u5DF2\u542F\u52A8"),console.log(` Agent: ${o}`),console.log(` PID: ${s.pid}`),console.log(` Log: ~/.aitable-workflow/daemons/${o}/daemon.log`)}catch(o){A.error("cli","start \u5931\u8D25",{error:o instanceof Error?o.message:String(o)}),process.exit(1)}}),t.command("stop").description("\u505C\u6B62\u540E\u53F0 daemon").option("--agent <name>","Agent \u540D\u79F0").option("--all","\u505C\u6B62\u6240\u6709 daemon").action(async n=>{try{if(n.all){let o=await Bt(),i=0;for(let s of o)await de(s.name)&&i++;console.log(`\u2705 \u5DF2\u505C\u6B62 ${i} \u4E2A daemon`)}else{let o=n.agent;if(!o){let s=await j();if(!s)throw new Error("\u672A\u6307\u5B9A --agent \u4E14\u65E0\u6D3B\u8DC3 Agent");o=s.name}let i=await de(o);console.log(i?`\u2705 Daemon "${o}" \u5DF2\u505C\u6B62`:`\u26A0\uFE0F Daemon "${o}" \u672A\u5728\u8FD0\u884C`)}}catch(o){A.error("cli","stop \u5931\u8D25",{error:o instanceof Error?o.message:String(o)}),process.exit(1)}}),t.command("restart").description("\u91CD\u542F\u540E\u53F0 daemon").option("--agent <name>","Agent \u540D\u79F0").option("--max-tasks <n>","\u5E76\u53D1\u4E0A\u9650","1").action(async n=>{try{let o=n.agent;if(!o){let a=await j();if(!a)throw new Error("\u672A\u6307\u5B9A --agent \u4E14\u65E0\u6D3B\u8DC3 Agent");o=a.name}console.log(`\u{1F504} \u6B63\u5728\u91CD\u542F "${o}"...`),await de(o);let i=await B({name:o,maxTasks:n.maxTasks}),s=await K({agentId:o,serverUrl:i.serverUrl,token:i.token,key:i.key,name:i.name??o,workspaceRoot:i.workspaceRoot,machineId:i.machineId,maxTasks:i.maxTasks});console.log(`\u2705 Daemon \u5DF2\u91CD\u542F (PID: ${s.pid})`)}catch(o){A.error("cli","restart \u5931\u8D25",{error:o instanceof Error?o.message:String(o)}),process.exit(1)}}),t.command("status").description("\u67E5\u770B daemon \u72B6\u6001").option("--agent <name>","Agent \u540D\u79F0").option("-v, --verbose","\u5C55\u793A\u5B8C\u6574\u914D\u7F6E").action(async n=>{try{let o=n.agent;if(!o){let a=await j();if(!a){console.log("\u65E0\u6D3B\u8DC3 Agent");return}o=a.name}let i=await V(o),s=await j();if(console.log(`
|
|
57
|
+
Agent: ${i.agentId}`),console.log(`Running: ${i.running?"\u2705 yes":"\u274C no"}`),i.pid&&console.log(`PID: ${i.pid}`),i.uptimeMs!==null){let a=Math.floor(i.uptimeMs/1e3),l=Math.floor(a/60),p=Math.floor(l/60);console.log(`Uptime: ${p}h ${l%60}m ${a%60}s`)}console.log(`Log file: ${i.logFile}`),n.verbose&&s&&(console.log(`
|
|
58
|
+
--- Configuration ---`),console.log(`Server: ${s.serverUrl}`),console.log(`Token: ${sr(s.token)}`),s.machineId&&console.log(`MachineID: ${s.machineId}`),s.workspaceRoot&&console.log(`Workspace: ${s.workspaceRoot}`)),console.log("")}catch(o){A.error("cli","status \u5931\u8D25",{error:o instanceof Error?o.message:String(o)}),process.exit(1)}}),t.command("logs").description("\u67E5\u770B daemon \u65E5\u5FD7").option("--agent <name>","Agent \u540D\u79F0").option("-f, --follow","\u6301\u7EED\u8DDF\u8E2A\u65E5\u5FD7\u8F93\u51FA").option("-n <lines>","\u663E\u793A\u6700\u8FD1 N \u884C","50").action(async n=>{try{let o=n.agent;if(!o){let a=await j();if(!a)throw new Error("\u672A\u6307\u5B9A --agent \u4E14\u65E0\u6D3B\u8DC3 Agent");o=a.name}let i=H(o),s=parseInt(n.n,10)||50;if(n.follow){let a=await Ut(i,"utf8").catch(()=>"");if(a){let p=a.trimEnd().split(`
|
|
59
|
+
`);console.log(p.slice(-s).join(`
|
|
60
|
+
`))}let l=a.length;ar(i,{interval:500},()=>{try{let p=cr(i);if(p.size>l){let d=lr(i,"r"),g=Buffer.alloc(p.size-l);pr(d,g,0,g.length,l),mr(d),process.stdout.write(g.toString("utf8")),l=p.size}}catch{}})}else{let a=await Ut(i,"utf8").catch(()=>"");if(!a){console.log(`\u6682\u65E0\u65E5\u5FD7 (${i})`);return}let l=a.trimEnd().split(`
|
|
61
61
|
`);console.log(l.slice(-s).join(`
|
|
62
|
-
`))}}catch(o){
|
|
63
|
-
Agent: ${n.agentId}`),console.log(`Installed: ${o.installed?"yes":"no"}`),console.log(`Loaded: ${o.loaded?"yes":"no"}`),console.log(`Running: ${o.running?`yes (PID: ${o.pid})`:"no"}`),console.log("")}else{let o=await Bt();if(o.length===0){console.log("\u65E0\u5DF2\u6CE8\u518C\u7684 Agent");return}for(let i of o){let s=await
|
|
62
|
+
`))}}catch(o){A.error("cli","logs \u5931\u8D25",{error:o instanceof Error?o.message:String(o)}),process.exit(1)}}),t.command("autostart").description("macOS \u5F00\u673A\u81EA\u542F\u7BA1\u7406").addCommand(new _e("enable").description("\u542F\u7528 macOS \u767B\u5F55\u81EA\u542F\u52A8").requiredOption("--agent-id <id>","Agent ID / \u540D\u79F0").option("--max-tasks <n>","\u5E76\u53D1\u4E0A\u9650").action(async n=>{try{P()||(console.error("\u274C \u5F00\u673A\u81EA\u542F\u5F53\u524D\u4EC5\u652F\u6301 macOS"),process.exit(1)),await Mt(n.agentId,{maxTasks:n.maxTasks?parseInt(n.maxTasks,10):void 0}),console.log("\u2705 \u81EA\u542F\u52A8\u5DF2\u542F\u7528"),console.log(` Agent: ${n.agentId}`),console.log(` Plist: ~/Library/LaunchAgents/com.alibaba.aitable-workflow.daemon.${n.agentId}.plist`)}catch(o){A.error("cli","autostart enable \u5931\u8D25",{error:o instanceof Error?o.message:String(o)}),process.exit(1)}})).addCommand(new _e("disable").description("\u5173\u95ED macOS \u767B\u5F55\u81EA\u542F\u52A8").requiredOption("--agent-id <id>","Agent ID / \u540D\u79F0").action(async n=>{try{P()||(console.error("\u274C \u5F00\u673A\u81EA\u542F\u5F53\u524D\u4EC5\u652F\u6301 macOS"),process.exit(1)),await Ft(n.agentId),console.log(`\u2705 \u81EA\u542F\u52A8\u5DF2\u5173\u95ED (${n.agentId})`)}catch(o){A.error("cli","autostart disable \u5931\u8D25",{error:o instanceof Error?o.message:String(o)}),process.exit(1)}})).addCommand(new _e("status").description("\u67E5\u770B macOS \u81EA\u542F\u52A8\u72B6\u6001").option("--agent-id <id>","Agent ID / \u540D\u79F0\uFF08\u4E0D\u6307\u5B9A\u5219\u5217\u51FA\u6240\u6709\uFF09").action(async n=>{try{if(P()||(console.error("\u274C \u5F00\u673A\u81EA\u542F\u5F53\u524D\u4EC5\u652F\u6301 macOS"),process.exit(1)),n.agentId){let o=await De(n.agentId);console.log(`
|
|
63
|
+
Agent: ${n.agentId}`),console.log(`Installed: ${o.installed?"yes":"no"}`),console.log(`Loaded: ${o.loaded?"yes":"no"}`),console.log(`Running: ${o.running?`yes (PID: ${o.pid})`:"no"}`),console.log("")}else{let o=await Bt();if(o.length===0){console.log("\u65E0\u5DF2\u6CE8\u518C\u7684 Agent");return}for(let i of o){let s=await De(i.name),a=s.running?`running (PID: ${s.pid})`:s.loaded?"loaded":s.installed?"installed":"not installed";console.log(` ${i.name}: ${a}`)}}}catch(o){A.error("cli","autostart status \u5931\u8D25",{error:o instanceof Error?o.message:String(o)}),process.exit(1)}}))}c(Nt,"registerDaemonCommands");function Wt(r,e){r.command("heartbeat").description("\u5411\u670D\u52A1\u7AEF\u53D1\u9001\u5FC3\u8DF3\u5E76\u8FD4\u56DE\u4EFB\u52A1\u5206\u914D\u60C5\u51B5").action(async()=>{try{let t=await j();if(!t)throw new Error("\u65E0\u6D3B\u8DC3 Agent");if(!(await V(t.name)).running){console.log("\u26A0\uFE0F Daemon \u672A\u5728\u8FD0\u884C\u3002\u4F7F\u7528 `aitable-workflow daemon start` \u542F\u52A8\u3002");return}let o=await ue(t.name);console.log("\u2705 Heartbeat OK"),console.log(` Agent: ${t.name}`),console.log(` Connection: ${o.connectionState}`),console.log(` Running: ${o.runningTasks} tasks`),console.log(` Queued: ${o.queuedTasks} tasks`),o.lastHeartbeatAt&&console.log(` Last HB: ${o.lastHeartbeatAt}`)}catch(t){A.error("cli","heartbeat \u5931\u8D25",{error:t instanceof Error?t.message:String(t)}),process.exit(1)}})}c(Wt,"registerHeartbeatCommand");import{logger as dr,saveCredential as gr,findConflictingCredential as ur,maskSecret as fr}from"aitable-workflow-core";function Gt(r,e){r.command("connect").description("\u4E00\u952E\u6CE8\u518C + \u4FDD\u5B58\u51ED\u8BC1 + \u540E\u53F0\u542F\u52A8 daemon\uFF08\u5BF9\u6807 Harness connect\uFF09").requiredOption("--server-url <url>","Gateway / \u5E73\u53F0\u5730\u5740").option("--token <ownerToken>","owner-token\uFF08\u81EA\u6CE8\u518C\uFF0C\u4E0E --key \u4E8C\u9009\u4E00\uFF09").option("--key <machineKey>","per-machine key\uFF08sk_machine_*\uFF0C\u4E0E --token \u4E8C\u9009\u4E00\uFF09").requiredOption("--name <name>","Agent \u540D\u79F0").option("--workspace-root <dir>","\u5DE5\u4F5C\u533A\u6839\u76EE\u5F55\uFF08\u53EF\u9009\uFF09").option("--no-start","\u53EA\u6CE8\u518C\u4E0D\u542F\u52A8 daemon").option("--foreground","\u6CE8\u518C\u540E\u5728\u524D\u53F0\u8FD0\u884C relay\uFF08\u963B\u585E\u7EC8\u7AEF\uFF0C\u4FBF\u4E8E\u8C03\u8BD5\uFF09").option("--force","\u8986\u76D6\u540C\u540D\u4F46\u7F51\u5173\u4E0D\u540C\u7684\u5DF2\u6709\u51ED\u8BC1").action(async t=>{try{if(!t.token&&!t.key)throw new Error("\u5FC5\u987B\u63D0\u4F9B --token\uFF08owner-token\uFF09\u6216 --key\uFF08per-machine key\uFF0Csk_machine_*\uFF09\u5176\u4E2D\u4E4B\u4E00\u3002");if(!t.force){let l=await ur(t.name,t.serverUrl);l&&(console.error(`\u26A0\uFE0F \u62D2\u7EDD\u8986\u76D6\uFF1AAgent \u540D\u79F0 "${t.name}" \u5DF2\u7ED1\u5B9A\u5230\u53E6\u4E00\u4E2A\u7F51\u5173\u3002`),console.error(` \u65E7\u7F51\u5173: ${l.serverUrl}`),console.error(` \u65B0\u7F51\u5173: ${t.serverUrl}`),console.error(" \u7EE7\u7EED\u5C06\u8986\u76D6\u672C\u673A\u8BE5\u51ED\u8BC1\uFF0C\u4E14\u65E7\u7F51\u5173\u7684 daemon \u65E0\u6CD5\u4E0E\u65B0\u7684\u5171\u5B58\uFF08PID \u4E92\u65A5\uFF09\u3002"),console.error(" \u8BF7\u9009\u62E9\u5176\u4E00\uFF1A"),console.error(" 1) \u6362\u4E00\u4E2A --name\uFF08\u591A\u7F51\u5173\u5EFA\u8BAE\u6BCF\u4E2A\u7F51\u5173\u7528\u4E0D\u540C\u540D\u79F0\uFF09"),console.error(" 2) \u786E\u5B9E\u8981\u6539\u7ED1\uFF0C\u52A0 --force \u5F3A\u5236\u8986\u76D6"),process.exit(1))}let n=t.workspaceRoot?Pe(t.workspaceRoot,t.name):void 0,o={name:t.name,serverUrl:t.serverUrl,token:t.token??"",key:t.key,workspaceRoot:n,createdAt:new Date().toISOString()};if(await gr(o),console.log(`\u2705 Agent "${t.name}" \u5DF2\u6CE8\u518C\u5E76\u4FDD\u5B58\u51ED\u8BC1`),console.log(` Server: ${t.serverUrl}`),console.log(` ${t.token?"Token":"Key"}: ${fr(t.token??t.key??"")}`),n&&console.log(` Workspace: ${n}`),console.log(" \u51ED\u8BC1\u6587\u4EF6: ~/.aitable-workflow/credentials.json"),!t.start){t.foreground&&console.log(`
|
|
64
64
|
\u{1F4A1} --no-start \u4E0E --foreground \u540C\u65F6\u6307\u5B9A\uFF0C\u6309 --no-start \u4F18\u5148\uFF1A\u4EC5\u6CE8\u518C\u4E0D\u542F\u52A8\u3002`),console.log(`
|
|
65
65
|
\u{1F4A1} \u542F\u52A8 daemon: aitable-workflow daemon start \u6216 aitable-workflow relay`);return}if(t.foreground){console.log(`
|
|
66
66
|
\u{1F680} \u524D\u53F0\u542F\u52A8 relay\uFF08Ctrl-C \u9000\u51FA\uFF09...`),await ge({serverUrl:t.serverUrl,token:t.token,key:t.key,name:t.name,workspaceRoot:n});return}let i=t.name,s=await B({serverUrl:t.serverUrl,token:t.token,key:t.key,workspaceRoot:t.workspaceRoot,name:i}),a=await K({agentId:i,serverUrl:s.serverUrl,token:s.token,key:s.key,name:s.name??i,workspaceRoot:s.workspaceRoot,machineId:s.machineId,maxTasks:s.maxTasks});console.log(`
|
|
67
67
|
\u{1F680} Daemon \u5DF2\u540E\u53F0\u542F\u52A8`),console.log(` Agent: ${i}`),console.log(` PID: ${a.pid}`),console.log(` Log: ~/.aitable-workflow/daemons/${i}/daemon.log`),console.log(`
|
|
68
|
-
\u{1F4CB} \u5E38\u7528\u547D\u4EE4\uFF1A`),console.log(` \u67E5\u770B\u72B6\u6001: aitable-workflow daemon status --agent ${i}`),console.log(` \u8DDF\u8E2A\u65E5\u5FD7: aitable-workflow daemon logs -f --agent ${i}`),console.log(` \u505C\u6B62: aitable-workflow daemon stop --agent ${i}`),console.log(` \u91CD\u542F: aitable-workflow daemon restart --agent ${i}`),console.log(" \u5217\u51FA Agent: aitable-workflow agent list"),console.log(" \u5207\u6362 Agent: aitable-workflow agent use <name>"),console.log(` \u5F00\u673A\u81EA\u542F: aitable-workflow daemon autostart enable --agent-id ${i}`)}catch(n){
|
|
68
|
+
\u{1F4CB} \u5E38\u7528\u547D\u4EE4\uFF1A`),console.log(` \u67E5\u770B\u72B6\u6001: aitable-workflow daemon status --agent ${i}`),console.log(` \u8DDF\u8E2A\u65E5\u5FD7: aitable-workflow daemon logs -f --agent ${i}`),console.log(` \u505C\u6B62: aitable-workflow daemon stop --agent ${i}`),console.log(` \u91CD\u542F: aitable-workflow daemon restart --agent ${i}`),console.log(" \u5217\u51FA Agent: aitable-workflow agent list"),console.log(" \u5207\u6362 Agent: aitable-workflow agent use <name>"),console.log(` \u5F00\u673A\u81EA\u542F: aitable-workflow daemon autostart enable --agent-id ${i}`)}catch(n){dr.error("cli","connect \u5931\u8D25",{error:n instanceof Error?n.message:String(n)}),process.exit(1)}})}c(Gt,"registerConnectCommand");import{logger as he,listCredentials as hr,setActiveAgent as wr,getActiveCredential as Ht,readCredentials as yr,maskSecret as qt}from"aitable-workflow-core";function Kt(r,e){let t=r.command("agent").description("Agent \u51ED\u8BC1\u7BA1\u7406");t.command("list").description("\u5217\u51FA\u6240\u6709\u5DF2\u4FDD\u5B58\u7684 Agent \u51ED\u8BC1").action(async()=>{try{let n=await hr();if(n.length===0){console.log("\u6682\u65E0\u5DF2\u4FDD\u5B58\u7684 Agent\u3002\u4F7F\u7528 `aitable-workflow connect` \u6CE8\u518C\u3002");return}let o=await yr();console.log(`
|
|
69
69
|
\u5DF2\u4FDD\u5B58 ${n.length} \u4E2A Agent\uFF08\u6D3B\u8DC3: ${o.activeAgent??"\u65E0"}\uFF09:
|
|
70
|
-
`);for(let i of n){let s=i.name===o.activeAgent?" \u2190 active":"";console.log(` \u2022 ${i.name}${s}`),console.log(` Server: ${i.serverUrl}`),console.log(` Token: ${qt(i.token)}`),i.machineId&&console.log(` MachineID: ${i.machineId}`),i.workspaceRoot&&console.log(` Workspace: ${i.workspaceRoot}`),console.log(` Created: ${i.createdAt}`)}console.log("")}catch(n){he.error("cli","agent list \u5931\u8D25",{error:n instanceof Error?n.message:String(n)}),process.exit(1)}}),t.command("use <name>").description("\u5207\u6362\u6D3B\u8DC3 Agent").action(async n=>{try{let o=await
|
|
70
|
+
`);for(let i of n){let s=i.name===o.activeAgent?" \u2190 active":"";console.log(` \u2022 ${i.name}${s}`),console.log(` Server: ${i.serverUrl}`),console.log(` Token: ${qt(i.token)}`),i.machineId&&console.log(` MachineID: ${i.machineId}`),i.workspaceRoot&&console.log(` Workspace: ${i.workspaceRoot}`),console.log(` Created: ${i.createdAt}`)}console.log("")}catch(n){he.error("cli","agent list \u5931\u8D25",{error:n instanceof Error?n.message:String(n)}),process.exit(1)}}),t.command("use <name>").description("\u5207\u6362\u6D3B\u8DC3 Agent").action(async n=>{try{let o=await wr(n);console.log(`\u2705 \u5DF2\u5207\u6362\u5230 Agent "${o.activeAgent}"`)}catch(o){he.error("cli","agent use \u5931\u8D25",{error:o instanceof Error?o.message:String(o)}),process.exit(1)}}),t.command("whoami").description("\u663E\u793A\u5F53\u524D\u6D3B\u8DC3 Agent").action(async()=>{try{let n=await Ht();if(!n){console.log("\u5F53\u524D\u65E0\u6D3B\u8DC3 Agent\u3002\u4F7F\u7528 `aitable-workflow connect` \u6CE8\u518C\u6216 `aitable-workflow agent use <name>` \u5207\u6362\u3002");return}console.log(`Agent: ${n.name}`),console.log(`Server: ${n.serverUrl}`),console.log(`Token: ${qt(n.token)}`),n.machineId&&console.log(`MachineID: ${n.machineId}`),n.workspaceRoot&&console.log(`Workspace: ${n.workspaceRoot}`)}catch(n){he.error("cli","agent whoami \u5931\u8D25",{error:n instanceof Error?n.message:String(n)}),process.exit(1)}}),t.command("status").description("\u67E5\u770B Agent \u8FDE\u63A5\u72B6\u6001\u4E0E\u8FD0\u884C\u65F6\u4FE1\u606F").option("--json","JSON \u683C\u5F0F\u8F93\u51FA").action(async n=>{try{let o=await Ht();if(!o){console.log("\u65E0\u6D3B\u8DC3 Agent\u3002\u4F7F\u7528 `aitable-workflow connect` \u6CE8\u518C\u3002");return}let i=await V(o.name),s=await ue(o.name),a={agent:o.name,serverUrl:o.serverUrl,connectionState:i.running?s.connectionState:"stopped",machineId:s.machineId??o.machineId??null,lastHeartbeatAt:s.lastHeartbeatAt,runningTasks:s.runningTasks,queuedTasks:s.queuedTasks,lastDisconnectReason:s.lastDisconnectReason,pid:i.pid};if(n.json){process.stdout.write(JSON.stringify(a,null,2)+`
|
|
71
71
|
`);return}let l=a.connectionState==="connected"?"\u{1F7E2}":a.connectionState==="reconnecting"?"\u{1F7E1}":"\u{1F534}";console.log(`
|
|
72
|
-
${l} Agent: ${a.agent}`),console.log(` Server: ${a.serverUrl}`),console.log(` Connection: ${a.connectionState}`),a.machineId&&console.log(` MachineID: ${a.machineId}`),a.lastHeartbeatAt&&console.log(` Last HB: ${a.lastHeartbeatAt}`),i.running&&(console.log(` PID: ${a.pid}`),console.log(` Tasks: ${a.runningTasks} running, ${a.queuedTasks} queued`)),a.lastDisconnectReason&&console.log(` Last Error: ${a.lastDisconnectReason}`),console.log("")}catch(o){he.error("cli","agent status \u5931\u8D25",{error:o instanceof Error?o.message:String(o)}),process.exit(1)}})}c(Kt,"registerAgentCommands");import{logger as
|
|
72
|
+
${l} Agent: ${a.agent}`),console.log(` Server: ${a.serverUrl}`),console.log(` Connection: ${a.connectionState}`),a.machineId&&console.log(` MachineID: ${a.machineId}`),a.lastHeartbeatAt&&console.log(` Last HB: ${a.lastHeartbeatAt}`),i.running&&(console.log(` PID: ${a.pid}`),console.log(` Tasks: ${a.runningTasks} running, ${a.queuedTasks} queued`)),a.lastDisconnectReason&&console.log(` Last Error: ${a.lastDisconnectReason}`),console.log("")}catch(o){he.error("cli","agent status \u5931\u8D25",{error:o instanceof Error?o.message:String(o)}),process.exit(1)}})}c(Kt,"registerAgentCommands");import{logger as vr}from"aitable-workflow-core";function Vt(r,e){r.command("relay").description("\u4EE5\u672C\u673A relay \u5B88\u62A4\u8EAB\u4EFD\u53CD\u8FDE RelayGateway\uFF0C\u6309\u9700\u5728\u672C\u673A\u6267\u884C Agent/dws \u4EFB\u52A1").option("--server-url <url>","Gateway \u5730\u5740\uFF08\u4E5F\u53EF\u7528 AITABLE_RELAY_SERVER_URL\uFF09").option("--key <machineKey>","per-machine key\uFF08sk_machine_*\uFF09").option("--token <ownerToken>","owner-token \u81EA\u6CE8\u518C\uFF08\u4E5F\u53EF\u7528 AITABLE_RELAY_TOKEN\uFF1B\u4F18\u5148\u4E8E --key\uFF09").option("--name <name>","\u81EA\u6CE8\u518C\u673A\u5668\u540D\uFF08\u53EF\u9009\uFF0C\u7F3A\u7701\u81EA\u52A8\u4F7F\u7528\u4E3B\u673A\u540D\uFF1Bowner+name \u5E42\u7B49\u952E\uFF0C\u591A\u673A\u573A\u666F\u5EFA\u8BAE\u663E\u5F0F\u6307\u5B9A\u4EE5\u907F\u514D hostname \u51B2\u7A81\u4E92\u76F8\u9876\u8FDE\u63A5\uFF1B\u4E5F\u53EF\u7528 AITABLE_RELAY_NAME\uFF09").option("--desc <description>","\u81EA\u6CE8\u518C\u673A\u5668\u63CF\u8FF0\uFF08\u4E5F\u53EF\u7528 AITABLE_RELAY_DESC\uFF09").option("--workspace-root <dir>","\u5DE5\u4F5C\u533A\u6839\u76EE\u5F55\uFF08\u4E5F\u53EF\u7528 AITABLE_RELAY_WORKSPACE_ROOT\uFF09").option("--machine-id <id>","\u7A33\u5B9A\u673A\u5668\u8EAB\u4EFD\uFF08\u4E5F\u53EF\u7528 AITABLE_RELAY_MACHINE_ID\uFF09").option("--max-tasks <n>","\u672C\u673A\u5E76\u53D1\u6267\u884C\u4E0A\u9650\uFF0C\u9ED8\u8BA4 1\uFF08\u4E5F\u53EF\u7528 AITABLE_RELAY_MAX_TASKS\uFF09").option("--max-workflows <n>","\u672C\u673A\u53EF\u540C\u65F6\u8FD0\u884C\u7684\u5DE5\u4F5C\u6D41\u5F15\u64CE\u6570\uFF0C\u9ED8\u8BA4 3\uFF1B0 = \u4E0D\u8BA4\u9886\u5DE5\u4F5C\u6D41\uFF08\u4E5F\u53EF\u7528 AITABLE_RELAY_MAX_WORKFLOWS\uFF09").action(t=>{try{ge(t)}catch(n){vr.error("cli","relay \u542F\u52A8\u5931\u8D25",{error:n instanceof Error?n.message:String(n)}),process.exit(1)}})}c(Vt,"registerRelayCommand");import{loadSettings as Yt,loadWorkflowTemplate as kr,checkWorkflowRecipes as br,Tracer as Cr}from"aitable-workflow-core";import Rr from"path";function Jt(r,e){r.command("inspect [recordId]").description("\u67E5\u770B\u5DE5\u5355\u6267\u884C trace\uFF1B\u6216\u7528 --recipes / --validate \u67E5\u770B\u6821\u9A8C recipe schema \u4E0E workflow \u5BF9\u9F50").option("-c, --config <path>","\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84",e.defaultConfig).option("-s, --step <stepId>","\u67E5\u770B\u7279\u5B9A\u6B65\u9AA4").option("-l, --latest","\u67E5\u770B\u6700\u8FD1\u6267\u884C").option("-n, --limit <number>","\u6700\u591A\u663E\u793A\u6761\u6570","10").option("--recipes","\u5217\u51FA\u5404\u6B65\u9AA4 recipe \u7684 input / output / config schema\u4E0E step \u914D\u7F6E").option("--validate","\u6821\u9A8C step.config / input_mapping / output_mapping \u662F\u5426\u7B26\u5408 recipe schema").option("-w, --workflow <id>","\u591A\u5DE5\u4F5C\u6D41\u6A21\u5F0F\uFF1A\u6307\u5B9A\u5DE5\u4F5C\u6D41 ID\uFF08\u7F3A\u7701\u53D6\u7B2C\u4E00\u4E2A\uFF09").action(async(t,n)=>{try{if(n.recipes||n.validate){await Ir(n);return}let o=await Yt(n.config),i=Rr.resolve(o.configDir,o.agent.trace_dir),s=new Cr(i),a=parseInt(n.limit,10)||10,l;if(n.latest||!t){if(l=await s.getLatestTraces(a),l.length===0){console.log("\u26A0\uFE0F \u6682\u65E0 trace \u8BB0\u5F55\u3002\u8FD0\u884C `run` \u547D\u4EE4\u540E\u518D\u67E5\u770B\u3002");return}console.log(`
|
|
73
73
|
\u{1F4C4} \u6700\u8FD1 ${l.length} \u6761\u6267\u884C\u8BB0\u5F55:
|
|
74
|
-
`)}else{if(l=await s.getTraces(t),l.length===0){console.log(`\u26A0\uFE0F \u672A\u627E\u5230\u5DE5\u5355 ${t} \u7684 trace \u8BB0\u5F55\u3002`);return}n.step&&(l=l.filter(
|
|
74
|
+
`)}else{if(l=await s.getTraces(t),l.length===0){console.log(`\u26A0\uFE0F \u672A\u627E\u5230\u5DE5\u5355 ${t} \u7684 trace \u8BB0\u5F55\u3002`);return}n.step&&(l=l.filter(p=>p.step_id===n.step)),console.log(`
|
|
75
75
|
\u{1F4C4} \u5DE5\u5355 ${t} \u7684\u6267\u884C\u5386\u53F2 (${l.length} \u6B65):
|
|
76
|
-
`)}for(let
|
|
76
|
+
`)}for(let p of l){let d=p.status==="success"?"\u2705":p.status==="failed"?"\u274C":"\u23F8\uFE0F",g=(p.duration_ms/1e3).toFixed(1);console.log(`${d} [${p.step_id}] ${p.step_name} \u2014 ${g}s \u2014 ${p.status.toUpperCase()}`);for(let[u,m]of p.llm_calls.entries()){let v=u===p.llm_calls.length-1,I=v?" \u2514\u2500":" \u251C\u2500",E=v?" ":" \u2502 ",z=(m.duration_ms/1e3).toFixed(1);console.log(`${I} LLM: ${m.model} (${z}s)`),console.log(`${E}Input: ${m.prompt_summary.slice(0,80)}${m.prompt_summary.length>80?"...":""}`),console.log(`${E}Output: ${m.response_summary.slice(0,100)}${m.response_summary.length>100?"...":""}`)}if(Object.keys(p.outputs).length>0){let u=Object.entries(p.outputs).map(([m,v])=>`${m}=${typeof v=="string"?v.slice(0,50):JSON.stringify(v)}`).join(", ");console.log(` \u2514\u2500 Outputs: ${u}`)}p.error&&console.log(` \u2514\u2500 Error: ${p.error}`),console.log()}}catch(o){console.error(`\u2717 inspect \u5931\u8D25: ${o instanceof Error?o.message:String(o)}`),process.exit(1)}})}c(Jt,"registerInspectCommand");async function Ir(r){let e=await Yt(r.config),t=r.workflow?e.workflowEntries.find(s=>s.id===r.workflow):e.workflowEntries[0];t||(console.error(`\u2717 \u672A\u627E\u5230\u5DE5\u4F5C\u6D41\u300C${r.workflow}\u300D\u3002\u53EF\u7528\uFF1A${e.workflowEntries.map(s=>s.id).join(", ")}`),process.exit(1));let n=await kr(t.templatePath,{envSource:e.envSource}),{issues:o,rows:i}=await br(t.recipeDir,n);if(r.recipes){console.log(`
|
|
77
77
|
\u{1F4CB} \u5DE5\u4F5C\u6D41\u300C${n.meta.name}\u300D(${t.id}) \u7684 recipe schema:
|
|
78
78
|
`);for(let s of i){let a=s.configSummary.length>0?s.configSummary.join(", "):"(none)",l=s.loaded?"":" \u26A0 \u52A0\u8F7D\u5931\u8D25";console.log(` [${s.stepId} \u2192 ${s.recipeId}]${l}`),console.log(` config: ${a}`),console.log()}}if(r.validate)if(o.length===0)console.log(`
|
|
79
79
|
\u2705 \u5DE5\u4F5C\u6D41\u300C${n.meta.name}\u300D(${t.id}) \u5168\u90E8\u6B65\u9AA4\u6821\u9A8C\u901A\u8FC7
|
|
80
80
|
`);else{let s=o.filter(l=>l.level==="error"),a=o.filter(l=>l.level==="warn");console.log(`
|
|
81
81
|
\u6821\u9A8C\u7ED3\u679C\uFF1A${s.length} \u4E2A\u9519\u8BEF\uFF0C${a.length} \u4E2A\u8B66\u544A
|
|
82
|
-
`);for(let l of o){let
|
|
82
|
+
`);for(let l of o){let p=l.level==="error"?"\u274C":"\u26A0\uFE0F ";console.log(` ${p} [${l.stepId} \u2192 ${l.recipeId}] ${l.message}`)}console.log(),s.length>0&&process.exit(1)}}c(Ir,"runRecipeInspect");import{runKbCompileCommand as Ar,runKbUpdateCommand as Sr,runKbStatusCommand as Er,runKbIngestCommand as xr,runKbCleanupCommand as Tr}from"aitable-workflow-core";function zt(r,e){let t=r.command("kb").description("\u77E5\u8BC6\u5E93(LLM-Wiki)\u7F16\u8BD1\u4E0E\u72B6\u6001\u7BA1\u7406");t.command("compile [collections...]").description("\u7F16\u8BD1\u77E5\u8BC6\u5E93\u4E3A wiki\uFF08\u4E0D\u6307\u5B9A collection \u65F6\u7F16\u8BD1\u89D2\u8272\u58F0\u660E\u7684\u5168\u90E8\uFF09").option("-c, --config <path>","\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84",e.defaultConfig).option("--full","\u5F3A\u5236\u5168\u91CF\u91CD\u7F16\u8BD1").action(async(n,o)=>{try{await Ar(n,o)}catch(i){console.error(`\u2717 kb compile \u5931\u8D25: ${i instanceof Error?i.message:String(i)}`),process.exit(1)}}),t.command("update [collections...]").description("\u6E90\u6587\u6863\u53D8\u66F4\u540E\u89E6\u53D1\u589E\u91CF\u7F16\u8BD1\uFF08\u672A\u53D8\u66F4\u5219\u8DF3\u8FC7\uFF09").option("-c, --config <path>","\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84",e.defaultConfig).action(async(n,o)=>{try{await Sr(n,o)}catch(i){console.error(`\u2717 kb update \u5931\u8D25: ${i instanceof Error?i.message:String(i)}`),process.exit(1)}}),t.command("status [collections...]").description("\u67E5\u770B\u77E5\u8BC6\u5E93\u7F16\u8BD1\u72B6\u6001\uFF08\u7F16\u8BD1\u4E2D/\u5B8C\u6210/\u5931\u8D25 + \u6E90\u662F\u5426\u5DF2\u53D8\u66F4\uFF09").option("-c, --config <path>","\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84",e.defaultConfig).action(async(n,o)=>{try{await Er(n,o)}catch(i){console.error(`\u2717 kb status \u5931\u8D25: ${i instanceof Error?i.message:String(i)}`),process.exit(1)}}),t.command("ingest <url-or-path>").description("\u5BFC\u5165\u6587\u6863\u5230 wiki \u76EE\u5F55\uFF08\u652F\u6301\u9489\u9489 URL \u6216\u672C\u5730\u8DEF\u5F84\uFF0Cdocx/pdf \u81EA\u52A8\u8F6C md\uFF09").option("-o, --output <dir>","\u8F93\u51FA\u76EE\u5F55\uFF08\u9ED8\u8BA4 ./wiki\uFF09").option("-t, --type <type>","\u6E90\u7C7B\u578B: dingtalk-doc\uFF08\u4E0D\u4F20\u5219\u81EA\u52A8\u8BC6\u522B\uFF09").option("--flat","\u4E0D\u4FDD\u7559\u8FDC\u7AEF\u76EE\u5F55\u7ED3\u6784\uFF08\u6240\u6709\u6587\u6863\u5E73\u94FA\u5230\u8F93\u51FA\u76EE\u5F55\uFF09").option("--force","\u5F3A\u5236\u91CD\u65B0\u62C9\u53D6\u6240\u6709\u6587\u6863\uFF08\u5FFD\u7565\u589E\u91CF\u5224\u65AD\uFF09").option("--compile","\u5BFC\u5165\u540E\u81EA\u52A8\u89E6\u53D1 kb compile").option("-c, --config <path>","\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84",e.defaultConfig).option("--timeout <ms>","\u5355\u6587\u6863\u8BF7\u6C42\u8D85\u65F6\uFF08\u6BEB\u79D2\uFF09").action(async(n,o)=>{try{await xr(n,o)}catch(i){console.error(`\u2717 kb ingest \u5931\u8D25: ${i instanceof Error?i.message:String(i)}`),process.exit(1)}}),t.command("cleanup <collection>").description("\u6E05\u7406\u77E5\u8BC6\u5E93\u4E2D\u7684\u5B64\u513F\u6587\u7AE0\uFF08\u6E90\u884C\u5DF2\u5220\u4F46 articles/ \u8FD8\u5728\uFF09").option("-c, --config <path>","\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84",e.defaultConfig).action(async(n,o)=>{try{await Tr(n,o)}catch(i){console.error(`\u2717 kb cleanup \u5931\u8D25: ${i instanceof Error?i.message:String(i)}`),process.exit(1)}})}c(zt,"registerKbCommands");import we from"path";import{BotProvisioner as $r,BotProvisionError as en,DwsDevRunner as Me,buildBotApprovalReportLines as Pr,defaultProvisionStatePath as Fe,groupEntriesOf as jr,loadProvisionState as tn,resolveConfigPath as Be,validateProvisionName as Or}from"aitable-workflow-core";import{logger as Xt}from"aitable-workflow-core";function Qt(r){for(let e of r){let t=e.title?`\u300C${e.title}\u300D${e.openConversationId}`:e.openConversationId;e.outcome==="added"?console.log(`
|
|
83
|
+
\u2705 \u7FA4 ${t}\uFF1A\u672C\u6B21\u5DF2\u771F\u5B9E\u8C03\u7528 add-bot \u5165\u7FA4${e.robotName?`\uFF08\u673A\u5668\u4EBA\u663E\u793A\u540D=${e.robotName}\uFF09`:""}\u3002`):e.source==="server"?console.log(`
|
|
84
|
+
\u2139\uFE0F \u7FA4 ${t}\uFF1A\u670D\u52A1\u7AEF\u786E\u8BA4\u8BE5\u7FA4\u5DF2\u5B89\u88C5\u6B64\u673A\u5668\u4EBA\uFF08500043\uFF09\uFF0C\u89C6\u540C\u5165\u7FA4\u6210\u529F\u3002`):(console.log(`
|
|
85
|
+
\u2139\uFE0F \u7FA4 ${t}\uFF1A\u5DF2\u5728\u7FA4\u53F0\u8D26\uFF08addedAt=${e.addedAt??"?"}\uFF09\uFF0C\u672C\u6B21\u672A\u53D1\u8D77\u4EFB\u4F55 API \u8C03\u7528\u3002`),console.log(" \u6CE8\u610F\uFF1A\u82E5\u673A\u5668\u4EBA\u66FE\u88AB\u624B\u52A8\u79FB\u51FA\u7FA4\uFF0C\u91CD\u8DD1\u672C\u547D\u4EE4\u4E0D\u4F1A\u91CD\u65B0\u6DFB\u52A0\uFF1B\u4EE5\u7FA4\u8BBE\u7F6E\u9875\u6216\u7FA4\u91CC @ \u5B9E\u6D4B\u4E3A\u51C6\u3002"))}}c(Qt,"printGroupOutcomes");function nn(r,e,t={}){let n=t.exit??(i=>process.exit(i)),o=r.command("bot").description("\u9489\u9489\u673A\u5668\u4EBA provisioning\uFF08\u5EFA\u53F7 / \u51ED\u636E\u7ED1\u5B9A / \u53D1\u5E03\u5BA1\u6279 / \u62C9\u7FA4\uFF09");o.command("provision").description("\u786E\u5B9A\u6027\u5EFA\u53F7\u5E76\u7ED1\u5B9A\u51ED\u636E\uFF08\u5E42\u7B49\u3001\u53EF\u65AD\u70B9\u7EED\u8DD1\uFF1B--publish/--group \u8D70\u4EBA\u5BA1\u4E0E\u62C9\u7FA4\u76F8\uFF09").requiredOption("--name <name>","\u5E94\u7528\u540D\uFF08\u4F01\u4E1A\u5185\u552F\u4E00\uFF0C2-20 \u5B57\uFF09").option("--robot-name <robotName>","\u673A\u5668\u4EBA\u663E\u793A\u540D\uFF08\u7F3A\u7701\u540C --name\uFF09").option("--desc <desc>","\u5E94\u7528\u63CF\u8FF0\uFF08\u2264200 \u5B57\uFF09").option("--bind <target>","\u51ED\u636E\u7ED1\u5B9A\u76EE\u6807\uFF1Aenv\uFF08\u5199 .env DINGTALK_ROBOTS\uFF09| none","env").option("--group <group>","\u62C9\u7FA4\u76EE\u6807\uFF1AopenConversationId \u6216\u7FA4\u540D\uFF0C\u9017\u53F7\u5206\u9694\u591A\u7FA4\uFF08\u9700\u5DF2\u5B8C\u6210 publish \u76F8\uFF1B\u5DF2\u5728\u7FA4\u53F0\u8D26\u7684\u7FA4\u96F6\u8C03\u7528\u8DF3\u8FC7\uFF09").option("--publish","\u8FDB\u5165\u53D1\u5E03\u76F8\uFF08\u771F\u5B9E\u6253\u6270\u7BA1\u7406\u5458\u5BA1\u6279\uFF0C\u9700\u914D\u5408 --approver-user-id \u786E\u8BA4\uFF09").option("--approver-user-id <userId>","\u5BA1\u6279\u4EBA userId\uFF08\u5019\u9009\u7531 check-approval \u5217\u51FA\uFF1B\u4EBA\u5BA1\u95E8\u4E0D\u5F97\u9759\u9ED8\u4EE3\u9009\uFF09").option("--state-file <path>","\u72B6\u6001\u6587\u4EF6\u8DEF\u5F84\uFF08\u7F3A\u7701 <configDir>/.aitable/provision-state.json\uFF09").option("--env-file <path>",".env \u6587\u4EF6\u8DEF\u5F84\uFF08\u7F3A\u7701 <configDir>/.env\uFF09").option("-c, --config <path>","\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84\uFF08\u7528\u4E8E\u5B9A\u4F4D configDir\uFF09",e.defaultConfig).option("--yes","\u4E0E dws \u8BED\u4E49\u5BF9\u9F50\uFF1A\u8DF3\u8FC7\u786E\u8BA4\uFF08provision \u5168\u7A0B\u975E\u4EA4\u4E92\uFF0C\u5199\u64CD\u4F5C\u5DF2\u5E26 --yes\uFF09").action(async i=>{try{let s=await Dr(i,e,t);s!==0&&n(s)}catch(s){Le(s),n(1)}}),o.command("status").description("\u67E5\u770B provision \u76F8\u4F4D / \u53D1\u5E03\u5BA1\u6279\u72B6\u6001 / \u5BA1\u6279\u4EBA\u5019\u9009\u4E0E\u50AC\u529E\u547D\u4EE4\u6A21\u677F").option("--state-file <path>","\u72B6\u6001\u6587\u4EF6\u8DEF\u5F84\uFF08\u7F3A\u7701 <configDir>/.aitable/provision-state.json\uFF09").option("-c, --config <path>","\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84\uFF08\u7528\u4E8E\u5B9A\u4F4D configDir\uFF09",e.defaultConfig).option("--refresh","\u5B9E\u65F6\u67E5\u8BE2 dws\uFF08version status + check-approval\uFF09\uFF0C\u800C\u975E\u53EA\u8BFB\u72B6\u6001\u6587\u4EF6").action(async i=>{try{let s=await Lr(i,e,t);s!==0&&n(s)}catch(s){Le(s),n(1)}});for(let[i,s]of[["disable","\u505C\u7528"],["enable","\u542F\u7528"]])o.command(i).description(`${s}\u673A\u5668\u4EBA\u80FD\u529B\u6216\u6574\u4E2A\u5E94\u7528\uFF08\u53EF\u9006\uFF1B--scope app \u4F5C\u7528\u4E8E\u6574\u4E2A\u5E94\u7528\uFF09`).option("--scope <scope>","\u4F5C\u7528\u8303\u56F4\uFF1Arobot\uFF08\u9ED8\u8BA4\uFF0C\u4EC5\u673A\u5668\u4EBA\u80FD\u529B\uFF09| app\uFF08\u6574\u4E2A\u5E94\u7528\uFF09","robot").option("--unified-app-id <id>","\u76EE\u6807\u5E94\u7528 unifiedAppId\uFF08\u7F3A\u7701\u4ECE\u72B6\u6001\u6587\u4EF6\u8BFB\uFF09").option("--state-file <path>","\u72B6\u6001\u6587\u4EF6\u8DEF\u5F84\uFF08\u7F3A\u7701 <configDir>/.aitable/provision-state.json\uFF09").option("-c, --config <path>","\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84\uFF08\u7528\u4E8E\u5B9A\u4F4D configDir\uFF09",e.defaultConfig).action(async a=>{try{let l=await Mr(i,a,e,t);l!==0&&n(l)}catch(l){Le(l),n(1)}})}c(nn,"registerBotCommands");async function Dr(r,e,t){let n=Or(r.name);if(n)return console.error(`\u274C ${n}`),1;if(r.bind&&r.bind!=="env"&&r.bind!=="none")return console.error(`\u274C --bind \u53EA\u652F\u6301 env | none\uFF0C\u6536\u5230: ${r.bind}`),1;let o=we.dirname(await Be(r.config??e.defaultConfig)),i=r.stateFile??Fe(o),s=r.envFile??we.join(o,".env");r.publish&&!r.approverUserId&&console.log("\u2139 --publish \u672A\u6307\u5B9A --approver-user-id\uFF1A\u82E5\u7EC4\u7EC7\u5BA1\u6279\u7B56\u7565\u9700\u9009\u4EBA\uFF0C\u5C06\u5217\u51FA\u5019\u9009\u540E\u505C\u4E0B\uFF0C\u7531\u4F60\u663E\u5F0F\u786E\u8BA4\u3002");let a=t.createProvisioner?.()??new $r({runner:new Me}),l={name:r.name,robotName:r.robotName,desc:r.desc,bind:r.bind??"env",envFile:s,stateFile:i,publish:r.publish,approverUserId:r.approverUserId,group:r.group};console.log(`\u{1F916} \u5F00\u59CB provision\u300C${r.name}\u300D\uFF08\u72B6\u6001\u6587\u4EF6: ${i}\uFF09`);let p=await a.provision(l);return _r(p,{stateFile:i,envFile:s,bind:l.bind??"env"})}c(Dr,"runProvision");function _r(r,e){switch(r.status){case"bound":{console.log(`
|
|
83
86
|
\u2705 \u51ED\u636E\u5DF2\u7ED1\u5B9A\uFF08bound \u76F8\uFF09\u2014\u2014\u5F15\u64CE\u5EFA\u8054\u53EF\u5148\u884C\u3002`),e.bind==="env"?(console.log(` \u51ED\u636E\u5199\u5165: ${e.envFile}\uFF08DINGTALK_ROBOTS\uFF0CcredRef=${r.state.clientId??"?"}\uFF09`),console.log(" \u8FD0\u884C\u4E2D\u7684 dingtalk_stream \u6E90\u4F1A\u7ECF ConfigHotReload \u70ED\u66F4\uFF0C\u65E0\u9700\u91CD\u542F\u3002")):console.log(" --bind none\uFF1A\u672A\u5199 .env\uFF1B\u9700\u8981\u65F6\u7528 `dws dev app credentials get` \u590D\u53D6\u3002"),console.log(" \u72B6\u6001\u6587\u4EF6:",e.stateFile),console.log("\n\u26A0\uFE0F Stream \u5355\u6D3E\u53D1\u4E92\u65A5\uFF1A\u52FF\u5BF9\u540C\u4E00\u673A\u5668\u4EBA\u540C\u65F6\u8FD0\u884C `dws dev connect`\uFF0C\u5426\u5219\u6D88\u606F\u53EA\u6D3E\u53D1\u5176\u4E00\u3002"),console.log("\u{1F4A1} \u771F\u5B9E\u7528\u6237\u6D41\u91CF\uFF08\u7FA4\u91CC @bot / \u5355\u804A\u53D1\u73B0\uFF09\u9700\u516C\u5F00\u53D1\u5E03\uFF1A`aitable-workflow bot provision --name <\u540C\u540D> --publish`");break}case"publish_pending":{console.log(`
|
|
84
87
|
\u23F3 \u53D1\u5E03\u5DF2\u63D0\u4EA4\uFF0C\u7B49\u5F85\u7BA1\u7406\u5458\u5BA1\u6279\uFF08publish_pending \u76F8\uFF09\u3002`),console.log(" \u67E5\u770B\u8FDB\u5EA6\u4E0E\u50AC\u529E: aitable-workflow bot status --refresh");break}case"published":{console.log(`
|
|
85
|
-
\u2705 \u5DF2\u53D1\u5E03\uFF08published \u76F8\uFF09\u2014\u2014\u771F\u5B9E\u7528\u6237\u6D41\u91CF\u53EF\u7528\u3002`),console.log("\u{1F4A1} \u62C9\u7FA4: `aitable-workflow bot provision --name <\u540C\u540D> --group <openConversationId|\u7FA4\u540D
|
|
86
|
-
\u2705 \u5DF2\u62C9\u7FA4\
|
|
87
|
-
\u26D4 \u516C\u5F00\u53D1\u5E03\u9700\u771F\u4EBA\u5BA1\u6279\uFF0C\u4E14\u672C\u7EC4\u7EC7\u7B56\u7565\u8981\u6C42\u663E\u5F0F\u9009\u5BA1\u6279\u4EBA\uFF08\u4EBA\u5BA1\u95E8\u4E0D\u5F97\u9759\u9ED8\u4EE3\u9009\uFF09\u3002`),console.error(" \u5BA1\u6279\u4EBA\u5019\u9009\uFF08\u6765\u81EA check-approval\uFF09:");for(let t of r.candidates)console.error(` - ${t.name??"(\u672A\u77E5)"} userId=${t.userId}${t.mainAdmin?"\uFF08\u4E3B\u7BA1\u7406\u5458\uFF09":""}`);return console.error(" \u786E\u8BA4\u540E\u91CD\u8DD1: aitable-workflow bot provision --name <\u540C\u540D> --publish --approver-user-id <userId>"),2}case"failed":return console.error(`
|
|
88
|
-
\u274C provision \u5931\u8D25: ${r.message}`),console.error(" \uFF08\u72B6\u6001\u6587\u4EF6\u5DF2\u4FDD\u7559\u8FDB\u5EA6\uFF0C\u4FEE\u590D\u540E\u53EF\u65AD\u70B9\u7EED\u8DD1: \u91CD\u8DD1\u540C\u4E00\u547D\u4EE4\uFF09"),1}return 0}c(
|
|
88
|
+
\u2705 \u5DF2\u53D1\u5E03\uFF08published \u76F8\uFF09\u2014\u2014\u771F\u5B9E\u7528\u6237\u6D41\u91CF\u53EF\u7528\u3002`),console.log("\u{1F4A1} \u62C9\u7FA4: `aitable-workflow bot provision --name <\u540C\u540D> --group <openConversationId|\u7FA4\u540D>`\uFF08\u591A\u7FA4\u9017\u53F7\u5206\u9694\uFF09");break}case"grouped":{if(r.groups.length===0){let t=Object.keys(jr(r.state)).length;console.log(`
|
|
89
|
+
\u2705 grouped \u76F8\uFF1A\u7FA4\u53F0\u8D26\u5DF2\u6709 ${t} \u4E2A\u7FA4\u3002\u672C\u6B21\u672A\u4F20 --group\uFF0C\u672A\u53D1\u8D77\u4EFB\u4F55\u62C9\u7FA4\u8C03\u7528\u3002`);break}Qt(r.groups),console.log("\u{1F4A1} add-bot \u7684 success \u53EA\u662F\u53D7\u7406\u4FE1\u53F7\uFF08dws \u65E0\u5728\u7FA4\u5217\u8868\u67E5\u8BE2\u63A5\u53E3\uFF09\uFF1A\u8BF7\u5230\u7FA4\u8BBE\u7F6E\u6838\u5BF9\u673A\u5668\u4EBA\u5728\u7FA4\u3002");break}case"approver_required":{console.error(`
|
|
90
|
+
\u26D4 \u516C\u5F00\u53D1\u5E03\u9700\u771F\u4EBA\u5BA1\u6279\uFF0C\u4E14\u672C\u7EC4\u7EC7\u7B56\u7565\u8981\u6C42\u663E\u5F0F\u9009\u5BA1\u6279\u4EBA\uFF08\u4EBA\u5BA1\u95E8\u4E0D\u5F97\u9759\u9ED8\u4EE3\u9009\uFF09\u3002`),console.error(" \u5BA1\u6279\u4EBA\u5019\u9009\uFF08\u6765\u81EA check-approval\uFF09:");for(let t of r.candidates)console.error(` - ${t.name??"(\u672A\u77E5)"} userId=${t.userId}${t.mainAdmin?"\uFF08\u4E3B\u7BA1\u7406\u5458\uFF09":""}`);return console.error(" \u786E\u8BA4\u540E\u91CD\u8DD1: aitable-workflow bot provision --name <\u540C\u540D> --publish --approver-user-id <userId>"),2}case"failed":return r.groups&&r.groups.length>0&&Qt(r.groups),console.error(`
|
|
91
|
+
\u274C provision \u5931\u8D25: ${r.message}`),console.error(" \uFF08\u72B6\u6001\u6587\u4EF6\u5DF2\u4FDD\u7559\u8FDB\u5EA6\uFF0C\u4FEE\u590D\u540E\u53EF\u65AD\u70B9\u7EED\u8DD1: \u91CD\u8DD1\u540C\u4E00\u547D\u4EE4\uFF09"),1}return 0}c(_r,"printProvisionResult");async function Lr(r,e,t){let n=we.dirname(await Be(r.config??e.defaultConfig)),o=r.stateFile??Fe(n),i=await tn(o),s,a=null,l=null;if(r.refresh&&i?.unifiedAppId&&i.versionId){let d=t.createRunner?.()??new Me;try{s=(await d.versionStatus(i.unifiedAppId,i.versionId)).versionStatus}catch(g){l=Zt(g)}if(!l)try{a=await d.checkApproval(i.unifiedAppId,i.versionId)}catch(g){l=Zt(g)}}let p=Pr({state:i,versionStatus:s,approval:a,error:l});return console.log(p.map(d=>` ${d}`).join(`
|
|
89
92
|
`)),console.log(`
|
|
90
|
-
\u72B6\u6001\u6587\u4EF6: ${o}`),i&&!r.refresh&&(i.phase==="publish_pending"||i.phase==="version_created")&&console.log(" \u63D0\u793A: \u52A0 --refresh \u5B9E\u65F6\u67E5\u8BE2\u5BA1\u6279\u72B6\u6001\u4E0E\u5019\u9009\u3002"),i?0:1}c(
|
|
91
|
-
\u2705 \u5DF2${s}${l}\u3002`),r==="disable"&&(console.log(` \u6062\u590D: ${o==="app"?"aitable-workflow bot enable --scope app":"aitable-workflow bot enable"}\uFF08\u542F\u505C\u53EF\u9006\uFF1B.env \u51ED\u636E\u4E0E\u72B6\u6001\u6587\u4EF6\u4E0D\u53D7\u5F71\u54CD\uFF09`),console.log(" \u26A0\uFE0F \u505C\u7528\u540E Stream \u5EFA\u8054/\u6D88\u606F\u6536\u53D1\u4F1A\u5931\u8D25\uFF1B\u8FD0\u884C\u4E2D\u7684\u5F15\u64CE\u4F1A\u6301\u7EED\u62A5\u9519\u76F4\u5230\u6062\u590D\u3002")),0}c(
|
|
93
|
+
\u72B6\u6001\u6587\u4EF6: ${o}`),i&&!r.refresh&&(i.phase==="publish_pending"||i.phase==="version_created")&&console.log(" \u63D0\u793A: \u52A0 --refresh \u5B9E\u65F6\u67E5\u8BE2\u5BA1\u6279\u72B6\u6001\u4E0E\u5019\u9009\u3002"),i?0:1}c(Lr,"runStatus");function Zt(r){return r instanceof en?`${r.message}\uFF08${r.guidance}\uFF09`:r instanceof Error?r.message:String(r)}c(Zt,"describeError");async function Mr(r,e,t,n){let o=e.scope??"robot";if(o!=="robot"&&o!=="app")return console.error(`\u274C --scope \u53EA\u652F\u6301 robot | app\uFF0C\u6536\u5230: ${o}`),1;let i=e.unifiedAppId?.trim();if(!i){let d=we.dirname(await Be(e.config??t.defaultConfig)),g=e.stateFile??Fe(d);if(i=(await tn(g))?.unifiedAppId??void 0,!i)return console.error(`\u274C \u65E0\u6CD5\u786E\u5B9A\u76EE\u6807\u5E94\u7528\uFF1A\u72B6\u6001\u6587\u4EF6\u65E0 unifiedAppId\uFF08${g}\uFF09\uFF0C\u8BF7\u7528 --unified-app-id \u663E\u5F0F\u6307\u5B9A\u3002`),1}let s=r==="disable"?"\u505C\u7528":"\u542F\u7528",a=n.createRunner?.()??new Me,l=o==="app"?"\u6574\u4E2A\u5E94\u7528":"\u673A\u5668\u4EBA\u80FD\u529B";return console.log(`\u{1F916} ${s}${l}\uFF08unifiedAppId=${i}\uFF09`),o==="app"&&r==="disable"&&console.log("\u2139 --scope app \u4F1A\u505C\u7528\u6574\u4E2A\u5E94\u7528\uFF08\u673A\u5668\u4EBA/\u7F51\u9875\u5E94\u7528\u7B49\u5168\u90E8\u4E0D\u53EF\u7528\uFF09\uFF1B\u53EA\u60F3\u505C\u673A\u5668\u4EBA\u7528\u9ED8\u8BA4 --scope robot\u3002"),await{disable:{robot:a.disableRobot.bind(a),app:a.disableApp.bind(a)},enable:{robot:a.enableRobot.bind(a),app:a.enableApp.bind(a)}}[r][o](i),console.log(`
|
|
94
|
+
\u2705 \u5DF2${s}${l}\u3002`),r==="disable"&&(console.log(` \u6062\u590D: ${o==="app"?"aitable-workflow bot enable --scope app":"aitable-workflow bot enable"}\uFF08\u542F\u505C\u53EF\u9006\uFF1B.env \u51ED\u636E\u4E0E\u72B6\u6001\u6587\u4EF6\u4E0D\u53D7\u5F71\u54CD\uFF09`),console.log(" \u26A0\uFE0F \u505C\u7528\u540E Stream \u5EFA\u8054/\u6D88\u606F\u6536\u53D1\u4F1A\u5931\u8D25\uFF1B\u8FD0\u884C\u4E2D\u7684\u5F15\u64CE\u4F1A\u6301\u7EED\u62A5\u9519\u76F4\u5230\u6062\u590D\u3002")),0}c(Mr,"runToggle");function Le(r){if(r instanceof en)Xt.error("cli","bot \u547D\u4EE4\u5931\u8D25",{kind:r.kind}),console.error(`
|
|
92
95
|
\u274C ${r.message}`),console.error(` \u21B3 ${r.guidance}`);else{let e=r instanceof Error?r.message:String(r);Xt.error("cli","bot \u547D\u4EE4\u5931\u8D25",{error:e}),console.error(`
|
|
93
|
-
\u274C ${e}`)}}c(Le,"failWithDiagnostics");var sn
|
|
96
|
+
\u274C ${e}`)}}c(Le,"failWithDiagnostics");var an=sn.dirname(Nr(import.meta.url));rn({override:!1,quiet:!0});rn({path:sn.resolve(an,"..",".env"),override:!1,quiet:!0});var cn=Ve(),Ue=ke(cn),Wr=Ye(cn);Ur({version:Ue});var f=new Fr().name("aitable-workflow").description("\u901A\u7528\u4E1A\u52A1\u6D41\u7A0B\u5F15\u64CE \u2014 \u914D\u7F6E\u9A71\u52A8\u7684 AI \u8868\u683C\u5DE5\u4F5C\u6D41\u81EA\u52A8\u5316").version(Ue).enablePositionalOptions().option("-d, --debug","\u8F93\u51FA debug \u7EA7\u522B\u65E5\u5FD7\uFF08\u542B recipe \u5185\u90E8 log\uFF09\uFF1B\u9700\u5199\u5728\u547D\u4EE4\u540D\u4E4B\u524D").hook("preAction",(r,e)=>{r.opts().debug?on.setLevel("debug"):(e.name()==="config"||e.parent?.name()==="config")&&on.setLevel("warn")}).hook("preAction",async(r,e)=>{e.name()!=="init"&&await Br()}),h={cliDir:an,version:Ue,packageVersions:Wr,defaultConfig:Je};ze(f,h);Xe(f,h);Qe(f,h);tt(f,h);rt(f,h);st(f,h);lt(f,h);pt(f,h);We(f,h);mt(f,h);dt(f,h);gt(f,h);ft(f,h);Nt(f,h);Gt(f,h);Kt(f,h);Wt(f,h);Vt(f,h);Jt(f,h);zt(f,h);nn(f,h);await f.parseAsync();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aitable-workflow-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.21-beta.1787884824605",
|
|
4
4
|
"description": "aitable-workflow 命令行工具(cli):init/design/setup/run/serve 等命令;含 relay 本地反连客户端与 daemon 进程管理。bin: aitable-workflow。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -19,8 +19,8 @@
|
|
|
19
19
|
"access": "public"
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"aitable-workflow-core": "0.1.
|
|
23
|
-
"aitable-workflow-server": "0.1.
|
|
22
|
+
"aitable-workflow-core": "0.1.21-beta.1787884824605",
|
|
23
|
+
"aitable-workflow-server": "0.1.21-beta.1787884824605",
|
|
24
24
|
"@inquirer/prompts": "^8.5.2",
|
|
25
25
|
"commander": "^14.0.2",
|
|
26
26
|
"dotenv": "^17.4.2",
|
|
@@ -78,6 +78,16 @@ describe("resolveGroupMention — 判定与降级", () => {
|
|
|
78
78
|
"chat", "group", "members", "list-by-ids", "--id", "cid1", "--users", "open-owner",
|
|
79
79
|
]);
|
|
80
80
|
});
|
|
81
|
+
|
|
82
|
+
it("指定群归属企业时显式传 --profile", async () => {
|
|
83
|
+
let seen: string[] = [];
|
|
84
|
+
const dws = fakeDws(async (args) => { seen = args; return { result: { members: [] } }; });
|
|
85
|
+
await resolveGroupMention("cid1", CONTACT, { dws, corpId: " corp-b ", now: () => 1 });
|
|
86
|
+
expect(seen).toEqual([
|
|
87
|
+
"chat", "group", "members", "list-by-ids", "--id", "cid1", "--users", "open-owner",
|
|
88
|
+
"--profile", "corp-b",
|
|
89
|
+
]);
|
|
90
|
+
});
|
|
81
91
|
});
|
|
82
92
|
|
|
83
93
|
describe("resolveGroupMention — 缓存", () => {
|
|
@@ -104,6 +114,13 @@ describe("resolveGroupMention — 缓存", () => {
|
|
|
104
114
|
await resolveGroupMention("cid1", { userId: "999", openDingTalkId: "open-other" }, { dws, now: () => 1 });
|
|
105
115
|
expect(dws.runJson).toHaveBeenCalledTimes(3);
|
|
106
116
|
});
|
|
117
|
+
|
|
118
|
+
it("同群同人在不同企业 corpId 下不共享缓存", async () => {
|
|
119
|
+
const dws = fakeDws(async () => ({ result: { members: [{ openDingTalkId: "open-owner" }] } }));
|
|
120
|
+
await resolveGroupMention("cid1", CONTACT, { dws, corpId: "corp-a", now: () => 1 });
|
|
121
|
+
await resolveGroupMention("cid1", CONTACT, { dws, corpId: "corp-b", now: () => 1 });
|
|
122
|
+
expect(dws.runJson).toHaveBeenCalledTimes(2);
|
|
123
|
+
});
|
|
107
124
|
});
|
|
108
125
|
|
|
109
126
|
describe("走 DwsClient 带来的可观测性收益", () => {
|
|
@@ -34,6 +34,8 @@ export type GroupMembershipDeps = {
|
|
|
34
34
|
dws?: Pick<DwsClient, "runJson">;
|
|
35
35
|
/** dws 可执行文件名(对齐 settings.tracker.dws_bin),缺省 "dws" */
|
|
36
36
|
dwsBin?: string;
|
|
37
|
+
/** 群归属企业 corpId;Webhook 出站必须显式传入 */
|
|
38
|
+
corpId?: string;
|
|
37
39
|
/** 注入时钟(测试用) */
|
|
38
40
|
now?: () => number;
|
|
39
41
|
};
|
|
@@ -103,6 +105,7 @@ export async function resolveGroupMention(
|
|
|
103
105
|
const cid = String(conversationId ?? "").trim();
|
|
104
106
|
const userId = String(candidate?.userId ?? "").trim();
|
|
105
107
|
const openDingTalkId = String(candidate?.openDingTalkId ?? "").trim();
|
|
108
|
+
const corpId = String(deps.corpId ?? "").trim();
|
|
106
109
|
const fallback: GroupMention = { mode: "name" };
|
|
107
110
|
|
|
108
111
|
if (!cid || !userId || !openDingTalkId) {
|
|
@@ -113,7 +116,8 @@ export async function resolveGroupMention(
|
|
|
113
116
|
}
|
|
114
117
|
|
|
115
118
|
const now = (deps.now ?? Date.now)();
|
|
116
|
-
|
|
119
|
+
// userId 与群成员可见性均属于企业命名空间,corpId 必须进入缓存键,避免跨企业串值。
|
|
120
|
+
const cacheKey = `${corpId}\u0000${cid}\u0000${userId}\u0000${openDingTalkId}`;
|
|
117
121
|
const cached = getCached(cacheKey, now);
|
|
118
122
|
if (cached !== undefined) return cached;
|
|
119
123
|
|
|
@@ -122,8 +126,11 @@ export async function resolveGroupMention(
|
|
|
122
126
|
let result: GroupMention = fallback;
|
|
123
127
|
try {
|
|
124
128
|
// DwsClient.runJson 自动追加 --format json,这里不再手写
|
|
129
|
+
const args = ["chat", "group", "members", "list-by-ids", "--id", cid, "--users", openDingTalkId];
|
|
130
|
+
// --profile 是 DWS 的全局认证上下文参数,支持直接传 corpId;它不是群成员接口的业务字段。
|
|
131
|
+
if (corpId) args.push("--profile", corpId);
|
|
125
132
|
const payload = await dws.runJson(
|
|
126
|
-
|
|
133
|
+
args,
|
|
127
134
|
{ timeoutMs: DWS_TIMEOUT_MS },
|
|
128
135
|
);
|
|
129
136
|
const matched = memberList(payload).some((m) => readOpenDingTalkId(m) === openDingTalkId);
|
|
@@ -85,6 +85,8 @@ workflows:
|
|
|
85
85
|
on_action:
|
|
86
86
|
# 功能模块人员配置来源(config-sync 发布):bug/feature/kb_gap 升级时据此解析模块 Owner
|
|
87
87
|
module_owners: "${globalVar.module_owners}"
|
|
88
|
+
# Webhook 群必须配置 corpId 才允许真实 @;缺失时 Owner 尾注降级为花名
|
|
89
|
+
group_configs: "${globalVar.group_configs}"
|
|
88
90
|
policies:
|
|
89
91
|
reply: { track: true, group: answer }
|
|
90
92
|
bug: { track: true, group: answer }
|
|
@@ -115,6 +115,8 @@ workflows:
|
|
|
115
115
|
on_action:
|
|
116
116
|
# 功能模块人员配置来源(config-sync 发布):bug/feature/kb_gap 升级时据此解析模块 Owner
|
|
117
117
|
module_owners: "${globalVar.module_owners}"
|
|
118
|
+
# Webhook 群必须配置 corpId 才允许真实 @;缺失时 Owner 尾注降级为花名
|
|
119
|
+
group_configs: "${globalVar.group_configs}"
|
|
118
120
|
policies:
|
|
119
121
|
reply: { track: true, group: answer } # 有据回答:发群
|
|
120
122
|
bug: { track: true, group: answer } # Bug 升级:发群(Owner 可解析则自动附尾注)
|
|
@@ -9,6 +9,14 @@ import { resolveOwner, type OwnerResolution } from "../../../lib/owner-resolver.
|
|
|
9
9
|
import { buildActionDispatch, needsOwnerResolution, shouldRecordOwnerFailure } from "../../../lib/action-dispatch.js";
|
|
10
10
|
import { SUPPORT_MEMORY_KINDS, SUPPORT_MEMORY_ROLES, supportEntry, supportScope } from "../../../lib/memory-events.js";
|
|
11
11
|
|
|
12
|
+
type GroupConfig = { groupId: string; outbound?: string; corpId?: string };
|
|
13
|
+
|
|
14
|
+
function loadGroupConfig(ctx: RecipeContext, source: unknown, conversationId: string): GroupConfig | null {
|
|
15
|
+
const list = resolveGlobalVarRef(source, ctx.getGlobalVar) as GroupConfig[] | undefined;
|
|
16
|
+
if (!Array.isArray(list)) return null;
|
|
17
|
+
return list.find((group) => group.groupId === conversationId) ?? null;
|
|
18
|
+
}
|
|
19
|
+
|
|
12
20
|
/**
|
|
13
21
|
* 决策后处置分发(support-qa 尾前步,取代 on-silence——所有最终处置的统一分发器,spec §10)
|
|
14
22
|
*
|
|
@@ -47,6 +55,10 @@ const recipe: RecipeDefinition = {
|
|
|
47
55
|
.union([z.string(), z.array(z.unknown())])
|
|
48
56
|
.optional()
|
|
49
57
|
.describe("功能模块人员配置来源(二选一):静态数组 [{module, owners:[{userId, nick, openDingTalkId}]}],或引用 ${globalVar.module_owners}(config-sync 从 AI 表格发布)。缺省取后者。"),
|
|
58
|
+
group_configs: z
|
|
59
|
+
.union([z.string(), z.array(z.unknown())])
|
|
60
|
+
.optional()
|
|
61
|
+
.describe("群配置来源;Webhook 群只有 corpId 非空时才允许真实 @ 人。缺省取 ${globalVar.group_configs}"),
|
|
50
62
|
}),
|
|
51
63
|
},
|
|
52
64
|
|
|
@@ -86,13 +98,23 @@ const recipe: RecipeDefinition = {
|
|
|
86
98
|
const ownersSource = ctx.config.module_owners ?? "${globalVar.module_owners}";
|
|
87
99
|
owner = resolveOwner(resolveGlobalVarRef(ownersSource, ctx.getGlobalVar), module);
|
|
88
100
|
if (owner.status === "resolved") {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
101
|
+
const groupsSource = ctx.config.group_configs ?? "${globalVar.group_configs}";
|
|
102
|
+
const group = loadGroupConfig(ctx, groupsSource, conversationId);
|
|
103
|
+
const webhookCorpId = group?.outbound === "webhook" ? String(group.corpId ?? "").trim() : "";
|
|
104
|
+
if (group?.outbound === "webhook" && !webhookCorpId) {
|
|
105
|
+
// Webhook 的 userId 属于企业命名空间。群归属企业未知时不能猜测当前 DWS
|
|
106
|
+
// 默认组织,否则可能把无效 userId 作为裸 `@userId` 发进跨企业群。
|
|
107
|
+
log(`Webhook 群 ${conversationId || "(空)"} 未配置 corpId,Owner 降级为花名文本`);
|
|
108
|
+
} else {
|
|
109
|
+
// 能否真实 @:校验 Owner 在不在当前群;Webhook 群通过 `--profile <corpId>` 固定认证组织。
|
|
110
|
+
// 任何失败降级为 mode=name → 下游只渲染花名,不写假 @。
|
|
111
|
+
const mention = await resolveGroupMention(
|
|
112
|
+
conversationId,
|
|
113
|
+
{ userId: owner.ownerUserId, openDingTalkId: owner.ownerOpenDingTalkId },
|
|
114
|
+
webhookCorpId ? { corpId: webhookCorpId } : {},
|
|
115
|
+
);
|
|
116
|
+
if (mention.mode === "at") ownerMentionUserId = mention.userId;
|
|
117
|
+
}
|
|
96
118
|
}
|
|
97
119
|
// 失败事件按 shouldRecordOwnerFailure 过滤:bug/feature 恒记;
|
|
98
120
|
// kb_gap 仅「给了 module 却查不到人」才记(否则大量无 module 的常态 kb_gap 会刷满事件)。
|
|
@@ -132,8 +154,15 @@ const recipe: RecipeDefinition = {
|
|
|
132
154
|
}
|
|
133
155
|
|
|
134
156
|
if (!policy.track) {
|
|
135
|
-
log(`处置(${action}) → 不留痕(track=false)`);
|
|
136
|
-
|
|
157
|
+
log(`处置(${action}) → 不留痕(track=false),group=${conversationId || "(未知)"}`);
|
|
158
|
+
// logs 会被 step-engine 的 no-op info 日志抬出去(静默处置唯一的运营可见点),
|
|
159
|
+
// 必须带群 ID + 理由摘要,否则「没收到」与「收到但判定不答」无法区分。
|
|
160
|
+
return {
|
|
161
|
+
status: "success",
|
|
162
|
+
output: {},
|
|
163
|
+
tracker: false,
|
|
164
|
+
logs: [`处置 ${action}:不留痕,group=${conversationId || "(未知)"},理由=${reason.slice(0, 120) || "(空)"}`],
|
|
165
|
+
};
|
|
137
166
|
}
|
|
138
167
|
|
|
139
168
|
const output: Record<string, unknown> = {
|
|
@@ -28,6 +28,12 @@ function loadGroupConfig(ctx: RecipeContext, source: unknown, conversationId: st
|
|
|
28
28
|
return list.find((g) => g.groupId === conversationId) ?? null;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
/** 兼容恢复中的旧 WorkItem:禁止把历史 Owner userId 作为裸 @ 占位符发进未知企业群。 */
|
|
32
|
+
function downgradeOwnerPlaceholder(content: string, userId: string): string {
|
|
33
|
+
const placeholder = `@${userId.trim()}`;
|
|
34
|
+
return placeholder === "@" ? content : content.split(placeholder).join("**相关负责人**");
|
|
35
|
+
}
|
|
36
|
+
|
|
31
37
|
/**
|
|
32
38
|
* 回复群消息(support-qa L0)
|
|
33
39
|
*
|
|
@@ -78,34 +84,44 @@ const recipe: RecipeDefinition = {
|
|
|
78
84
|
if (!answer.trim()) { log("无回答内容(静默),跳过"); return { status: "success", output: { reply_status: "静默" }, tracker: false, logs: ["跳过:无回答(静默)"] }; }
|
|
79
85
|
if (!conversationId) { log("缺少群ID,跳过"); return { status: "success", output: {}, tracker: false, logs: ["跳过:群ID 为空"] }; }
|
|
80
86
|
|
|
87
|
+
const isEscalation = needsOwnerResolution(action as SupportAction);
|
|
81
88
|
const group = loadGroupConfig(ctx, groupsSource, conversationId);
|
|
82
89
|
const options: ReplyToConversationOptions = {};
|
|
90
|
+
let outboundAnswer = answer;
|
|
83
91
|
if (group?.outbound === "webhook") {
|
|
84
92
|
options.preferWebhook = true;
|
|
85
93
|
if (group.credRef) options.credRef = group.credRef;
|
|
94
|
+
if (!String(group.corpId ?? "").trim()) {
|
|
95
|
+
// 当前群配置是发送时事实源:即使 WorkItem 带着旧 eventContext / mention_user_id,
|
|
96
|
+
// corpId 缺失时也必须关闭所有 Webhook @ 并清除正文里的历史数字占位符。
|
|
97
|
+
options.mentionSender = false;
|
|
98
|
+
outboundAnswer = downgradeOwnerPlaceholder(outboundAnswer, mentionUserId);
|
|
99
|
+
log(`Webhook 群 ${conversationId} 未配置 corpId,本次发送禁用 @ 人`);
|
|
100
|
+
}
|
|
86
101
|
}
|
|
87
102
|
// Owner @人(与尾注渲染同源):只有 on_action 已确认可在当前群真实 @
|
|
88
103
|
// 时 mention_user_id 才非空;否则保持缺省,仅 @提问者。
|
|
89
|
-
const
|
|
90
|
-
if (isEscalation && mentionUserId) {
|
|
104
|
+
const webhookMentionAllowed = group?.outbound !== "webhook" || Boolean(String(group.corpId ?? "").trim());
|
|
105
|
+
if (isEscalation && mentionUserId && webhookMentionAllowed) {
|
|
91
106
|
options.mentionUserIds = [mentionUserId];
|
|
92
107
|
}
|
|
108
|
+
const ownerMentioned = Boolean(options.mentionUserIds?.length);
|
|
93
109
|
|
|
94
110
|
try {
|
|
95
|
-
const res = await toolkit.messaging.replyToConversation(conversationId,
|
|
111
|
+
const res = await toolkit.messaging.replyToConversation(conversationId, outboundAnswer, options);
|
|
96
112
|
const via = res.via;
|
|
97
113
|
const reason = res.reason;
|
|
98
114
|
if (via === "skipped") {
|
|
99
115
|
log(`未能发送到群 ${conversationId} (via=skipped${reason ? `, reason=${reason}` : ""})`);
|
|
100
116
|
const skipReason = reason ?? "无可用出站通道";
|
|
101
117
|
// 重试风暴去重:同群+同待发内容+同原因在窗口内只留痕一次,重试只写日志
|
|
102
|
-
if (sendFailureWindow.mark(ctx.getGlobalVar, ctx.setGlobalVar, conversationId,
|
|
118
|
+
if (sendFailureWindow.mark(ctx.getGlobalVar, ctx.setGlobalVar, conversationId, outboundAnswer, `skipped:${skipReason}`)) {
|
|
103
119
|
try {
|
|
104
120
|
await toolkit.memory.append(
|
|
105
121
|
supportScope(conversationId),
|
|
106
122
|
supportEntry({
|
|
107
123
|
kind: SUPPORT_MEMORY_KINDS.sendFailed,
|
|
108
|
-
content:
|
|
124
|
+
content: outboundAnswer,
|
|
109
125
|
sender: "agent",
|
|
110
126
|
role: SUPPORT_MEMORY_ROLES.agent,
|
|
111
127
|
conversationId,
|
|
@@ -131,7 +147,7 @@ const recipe: RecipeDefinition = {
|
|
|
131
147
|
log(`已发送到群 ${conversationId} (via=${via}${reason ? `, reason=${reason}` : ""})`);
|
|
132
148
|
// 回声防护:发布出站内容指纹(conversation_handler 据此识别回流消息)
|
|
133
149
|
try {
|
|
134
|
-
echoWindow.mark(ctx.getGlobalVar, ctx.setGlobalVar,
|
|
150
|
+
echoWindow.mark(ctx.getGlobalVar, ctx.setGlobalVar, outboundAnswer);
|
|
135
151
|
} catch (fpErr) {
|
|
136
152
|
log(`发布回声指纹失败(不影响主流程): ${fpErr instanceof Error ? fpErr.message : String(fpErr)}`);
|
|
137
153
|
}
|
|
@@ -142,12 +158,12 @@ const recipe: RecipeDefinition = {
|
|
|
142
158
|
kind: isEscalation
|
|
143
159
|
? SUPPORT_MEMORY_KINDS.ownerEscalationSent
|
|
144
160
|
: SUPPORT_MEMORY_KINDS.agentReplySent,
|
|
145
|
-
content:
|
|
161
|
+
content: outboundAnswer,
|
|
146
162
|
sender: "agent",
|
|
147
163
|
role: SUPPORT_MEMORY_ROLES.agent,
|
|
148
164
|
conversationId,
|
|
149
|
-
summary: `已发送(via=${via}${
|
|
150
|
-
meta: { sendStatus: "sent", via, ...questionMeta, ...(action ? { action } : {}), ...(
|
|
165
|
+
summary: `已发送(via=${via}${ownerMentioned ? `, @Owner=${mentionUserId}` : ""}): ${outboundAnswer.slice(0, 80)}`,
|
|
166
|
+
meta: { sendStatus: "sent", via, ...questionMeta, ...(action ? { action } : {}), ...(ownerMentioned ? { ownerUserId: mentionUserId } : {}) },
|
|
151
167
|
}),
|
|
152
168
|
);
|
|
153
169
|
} catch (memErr) {
|
|
@@ -158,13 +174,13 @@ const recipe: RecipeDefinition = {
|
|
|
158
174
|
const msg = error instanceof Error ? error.message : String(error);
|
|
159
175
|
log(`发送失败: ${msg}`);
|
|
160
176
|
// 与 skipped 分支同源的重试风暴去重(异常原因不同则各记一次)
|
|
161
|
-
if (sendFailureWindow.mark(ctx.getGlobalVar, ctx.setGlobalVar, conversationId,
|
|
177
|
+
if (sendFailureWindow.mark(ctx.getGlobalVar, ctx.setGlobalVar, conversationId, outboundAnswer, `error:${msg}`)) {
|
|
162
178
|
try {
|
|
163
179
|
await toolkit.memory.append(
|
|
164
180
|
supportScope(conversationId),
|
|
165
181
|
supportEntry({
|
|
166
182
|
kind: SUPPORT_MEMORY_KINDS.sendFailed,
|
|
167
|
-
content:
|
|
183
|
+
content: outboundAnswer,
|
|
168
184
|
sender: "agent",
|
|
169
185
|
role: SUPPORT_MEMORY_ROLES.agent,
|
|
170
186
|
conversationId,
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
你是一名【群知识库答疑助手】。你的具体人设与负责的产品领域,以本次任务提示词开头注入的『# 角色设定』块为准(由 profile 的 `roles.support.persona` 提供),下文统称"你负责的产品"。
|
|
4
4
|
|
|
5
|
-
你的任务:收到一条群消息后,先按【三、闸门规则】判定处置动作(action),再基于【知识库】提供准确解答,并【只输出一个严格 JSON 对象】。知识库有据时就尽量把问题答清楚(默认目标);仅在无据 /
|
|
5
|
+
你的任务:收到一条群消息后,先按【三、闸门规则】判定处置动作(action),再基于【知识库】提供准确解答,并【只输出一个严格 JSON 对象】。知识库有据时就尽量把问题答清楚(默认目标);仅在无据 / 无据且域外 / 有风险时才判无 reply 类 action。
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -49,16 +49,24 @@
|
|
|
49
49
|
|
|
50
50
|
## 三、闸门规则:先定 action
|
|
51
51
|
|
|
52
|
-
在检索知识库、生成回复**之前**,先按以下闸门判定。**任一无 reply 类闸门命中即立即返回 JSON
|
|
52
|
+
在检索知识库、生成回复**之前**,先按以下闸门判定。**任一无 reply 类闸门命中即立即返回 JSON,跳过后续检索与作答。**例外是规则 6:对有效提问必须先完成检索、凭检索结果分流——「有据即答」不允许被域声明提前拦下。
|
|
53
53
|
|
|
54
54
|
1. **真人同事已介入** → `human_handled`:对话历史中标记为"(真人同事)"的人正在跟进当前问题,而当前消息只是用户向其补充信息或简单跟进;**同事之间的协作沟通**(互相转交、要材料、说"我排查下")同样命中。
|
|
55
55
|
2. **用户已主动 @ owner-map.json 名单内人员** → `owner_mentioned`:用户在当前消息或上下文中已 @ 了本目录 `owner-map.json` 名单中的某位真人负责人,默认礼让不回复。@ 名单外的人(包括非名单内的真人名/花名)不触发此规则,继续正常判断并检索作答。**例外 a**:问题在知识库中有明确完整答案、你非常有把握 → 仍可回答(减轻真人负担,此时直接作答即可,reply 不提任何人)。**例外 b**:见规则 7(追问补位)。
|
|
56
|
-
3. **非有效提问** → `no_question`:仅表示收到/感谢/赞同/寒暄;感谢+后续动作("谢谢我试试"
|
|
56
|
+
3. **非有效提问** → `no_question`:仅表示收到/感谢/赞同/寒暄;感谢+后续动作("谢谢我试试");闲聊、表情包、互动卡片、无说明的转发链接;对方案的简单跟进;对回答认可无追问;**复述你或他人之前说过的内容**。`no_question` 只覆盖「**非提问**」与「**无据且域外**」(见规则 6);无据但域内的提问按规则 6 分流到 `bug/feature/kb_gap`,不是 `no_question`。
|
|
57
57
|
4. **机器人已实质助力后避免过度回复** → `no_question`:历史中你已给出能解决问题的实质回答(或已判 bug/feature 升级过),而当前消息**无新线索、无新问题**(中性确认/观望、重复或换说法问原问题、围绕同一问题的情绪化追问)。
|
|
58
58
|
- ⚠️ **例外——上次是"无据回答"则不适用**:若你上次对该问题的回复是无据/未解决措辞("暂未找到相关说明"等),说明问题并未解决;用户重复提问要**重新检索知识库作答**——知识库可能已被管理员更新。
|
|
59
59
|
- 💡 **你自己的历史回复 ≠ 真人介入**:标记"(答疑机器人/即你自己之前的回复)"的是你自己,不要误触发规则 1。用户提供了**新线索/新问题**才回答。
|
|
60
60
|
5. **内容/安全命中** → `safety`(见「六、安全与防护」)。
|
|
61
|
-
6.
|
|
61
|
+
6. **有据即答优先,域只用来收窄**:对有效提问先按步骤 2 完成检索(这条要求**先完成检索**才能下结论),再按检索结果分流——
|
|
62
|
+
|
|
63
|
+
| 检索结果 | 问题位置 | action |
|
|
64
|
+
|---|---|---|
|
|
65
|
+
| 有据 | 任意(域内/域外/未声明域) | 进入【四】作答 |
|
|
66
|
+
| 无据 | persona 声明了负责域且问题在域内 | 按【四】问题类型路由判 `bug` / `feature` / `kb_gap` |
|
|
67
|
+
| 无据 | persona 未声明域,或问题在域外 | `no_question` |
|
|
68
|
+
|
|
69
|
+
负责域是**收紧开关**(品牌保护用),不是放行前提;放行的唯一前提是知识库有据。**无据判定必须带检索证据**:无 reply 类 action 的 reason 写清检索词 + 命中 0 篇 / 命中 N 篇但不相关 + 缺的是什么知识;知识库根不可访问(路径不存在/无权限/检索报错)时 reason 以 `kb_unreachable:` 开头(部署故障,与真知识缺口区分)。
|
|
62
70
|
7. **追问补位(仅当任务提示词中"追问补位参数"声明已启用时适用)**:用户曾 @ 真人负责人(规则 2 礼让过)、距该消息已超过声明的等待分钟数(依据历史 `[hh:mm]` 时间戳)、真人未回复、当前消息是用户对同一问题的**追问** → 不再礼让,正常检索作答;reply 开头加一句补位语气(如"看到相关同学暂时没回,先提供帮助文档里的信息:")。未启用时本条不存在,@真人后一律礼让。
|
|
63
71
|
|
|
64
72
|
> ⚠️ **核心判断标准**:用户消息中是否包含一个**需要你回答的具体问题或明确求助**?没有就判无 reply 类 action。**不确定时倾向无 reply 类**(漏答比错答安全)。
|
|
@@ -75,7 +83,7 @@
|
|
|
75
83
|
- **功能问询(默认)** → 进入步骤 2。
|
|
76
84
|
|
|
77
85
|
### 步骤 2:检索知识库(只读)
|
|
78
|
-
1.
|
|
86
|
+
1. 检索根 = 系统注入的知识库块所列路径,**必须整体作为搜索 path 传入**;从工作目录根递归搜不算检索过知识库(进不去软链挂载点,会「0 命中且不报错」)。按知识库导航说明定位候选文章:优先 Grep 关键词,再 Read 原文;
|
|
79
87
|
2. 必要时交叉参考多篇文档;
|
|
80
88
|
3. 消息含 `` 时先用 Read 看图(多为报错/操作截图)。
|
|
81
89
|
4. 工作目录下若有 `.long-term.md`(复盘沉淀的群聊趋势),可按需 Grep 辅助理解语境;**不作事实依据**。
|
|
@@ -87,7 +95,7 @@
|
|
|
87
95
|
| 知识库有**完整的**操作步骤/参数/答案 | ✅ 可答 | `reply` 直接回答 |
|
|
88
96
|
| 只覆盖**部分**内容,且剩余细节属于异常/不符预期 | ⚠️ 部分可答 | 按步骤 1「有据自助优先」判:覆盖的是该现象的常见原因/排查路径 → `reply` 自助不升级;仅真异常(完全无解释或有证据仍无法解释)→ `bug` + reply 答有据部分、标注无据细节需排查 |
|
|
89
97
|
| 只覆盖**部分**内容,剩余是普通知识细节 | ⚠️ 部分可答 | `reply` 答有据部分;无据细节明确标注"帮助文档中暂未提供具体说明"(不臆测数值/路径) |
|
|
90
|
-
| 知识库**完全未覆盖**(普通知识/资源/社区问题) | ❌ 不可答 | `kb_gap`,reason 写清缺什么知识 |
|
|
98
|
+
| 知识库**完全未覆盖**(普通知识/资源/社区问题) | ❌ 不可答 | `kb_gap`,reason 写清缺什么知识 + 检索证据(见规则 6) |
|
|
91
99
|
| 知识库对该现象**完全无解释**且属于真异常/明确改进诉求 | ❌ 不可答但需升级 | `bug` / `feature`(见步骤 1 路由) |
|
|
92
100
|
|
|
93
101
|
### 步骤 4:组织回答(按问题形态取用积木,不搞五段式)
|