openclaw-weiyuan-init 1.0.117 → 1.0.119
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/lib/commands.js +105 -42
- package/lib/downloader.js +64 -3
- package/package.json +1 -1
- package/workspace-weiyuan/weiyuan/README.md +1 -1
- package/workspace-weiyuan/weiyuan/src/cli.ts +114 -3
- package/workspace-weiyuan/weiyuan/src/cliMain.ts +3 -1
- package/workspace-weiyuan/weiyuan/src/skillAdapter.ts +126 -21
package/lib/commands.js
CHANGED
|
@@ -60,6 +60,15 @@ const DEFAULT_SKILL_TSCONFIG = {
|
|
|
60
60
|
|
|
61
61
|
const REQUIRED_SKILL_ACTIONS = ['project.list', 'task.list_all', 'context.candidates'];
|
|
62
62
|
|
|
63
|
+
function cleanCliRawText(raw) {
|
|
64
|
+
if (raw === undefined || raw === null) return '';
|
|
65
|
+
let text = String(raw).replace(/\r|\n/g, ' ').replace(/`/g, '').trim();
|
|
66
|
+
while ((text.startsWith('"') && text.endsWith('"')) || (text.startsWith("'") && text.endsWith("'"))) {
|
|
67
|
+
text = text.slice(1, -1).trim();
|
|
68
|
+
}
|
|
69
|
+
return text;
|
|
70
|
+
}
|
|
71
|
+
|
|
63
72
|
function bundledSkillMetadataPath(fileName) {
|
|
64
73
|
return path.join(__dirname, '..', 'workspace-weiyuan', 'weiyuan', fileName);
|
|
65
74
|
}
|
|
@@ -457,7 +466,7 @@ function resolveWorkspacePath(workspaceOption) {
|
|
|
457
466
|
function decodeInviteToken(token) {
|
|
458
467
|
if (!token || typeof token !== 'string') return null;
|
|
459
468
|
try {
|
|
460
|
-
const normalized = token.replace(/-/g, '+').replace(/_/g, '/');
|
|
469
|
+
const normalized = cleanCliRawText(token).replace(/-/g, '+').replace(/_/g, '/');
|
|
461
470
|
const pad = normalized.length % 4 === 0 ? '' : '='.repeat(4 - (normalized.length % 4));
|
|
462
471
|
const raw = Buffer.from(normalized + pad, 'base64').toString('utf8');
|
|
463
472
|
const parsed = JSON.parse(raw);
|
|
@@ -481,9 +490,10 @@ function decodeInviteToken(token) {
|
|
|
481
490
|
}
|
|
482
491
|
|
|
483
492
|
function normalizeServerUrl(raw) {
|
|
484
|
-
|
|
493
|
+
const cleaned = cleanCliRawText(raw);
|
|
494
|
+
if (!cleaned) return '';
|
|
485
495
|
try {
|
|
486
|
-
const url = new URL(
|
|
496
|
+
const url = new URL(cleaned);
|
|
487
497
|
const p = (url.pathname || '/').replace(/\/+$/, '');
|
|
488
498
|
if (!p || p === '/') url.pathname = '/api';
|
|
489
499
|
url.hash = '';
|
|
@@ -493,6 +503,30 @@ function normalizeServerUrl(raw) {
|
|
|
493
503
|
}
|
|
494
504
|
}
|
|
495
505
|
|
|
506
|
+
function normalizeUpgradeUrl(raw) {
|
|
507
|
+
const cleaned = cleanCliRawText(raw);
|
|
508
|
+
if (!cleaned) return '';
|
|
509
|
+
try {
|
|
510
|
+
const url = new URL(cleaned);
|
|
511
|
+
url.hash = '';
|
|
512
|
+
return url.toString().replace(/\/+$/, '');
|
|
513
|
+
} catch (_) {
|
|
514
|
+
return '';
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
function normalizeDownloadUrl(raw) {
|
|
519
|
+
const cleaned = cleanCliRawText(raw);
|
|
520
|
+
if (!cleaned) return '';
|
|
521
|
+
try {
|
|
522
|
+
const url = new URL(cleaned);
|
|
523
|
+
url.hash = '';
|
|
524
|
+
return url.toString();
|
|
525
|
+
} catch (_) {
|
|
526
|
+
return '';
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
|
|
496
530
|
function isAgentRegisterServerUrl(raw) {
|
|
497
531
|
if (!raw || typeof raw !== 'string') return false;
|
|
498
532
|
try {
|
|
@@ -641,6 +675,7 @@ async function printAgentConversationGuide(identityPath, projectId) {
|
|
|
641
675
|
async function ensureSkillRuntime(weiyuanPath) {
|
|
642
676
|
const pkgPath = path.join(weiyuanPath, 'package.json');
|
|
643
677
|
const tsconfigPath = path.join(weiyuanPath, 'tsconfig.json');
|
|
678
|
+
const bundledManifestPath = bundledSkillMetadataPath('manifest.json');
|
|
644
679
|
const capabilityCompatContent = `export const CAPABILITY_ACTION = {
|
|
645
680
|
PROJECT_DELETE: "project.delete",
|
|
646
681
|
TASK_DELETE: "task.delete",
|
|
@@ -656,8 +691,15 @@ async function ensureSkillRuntime(weiyuanPath) {
|
|
|
656
691
|
|
|
657
692
|
export const CAPABILITY_ACTION_ALLOWLIST = new Set<string>(Object.values(CAPABILITY_ACTION))
|
|
658
693
|
`;
|
|
694
|
+
let fallbackPackageJson = { ...DEFAULT_SKILL_PACKAGE_JSON };
|
|
695
|
+
try {
|
|
696
|
+
const bundledManifest = await fs.readJson(bundledManifestPath);
|
|
697
|
+
const bundledVersion = String(bundledManifest && bundledManifest.version || '').trim();
|
|
698
|
+
if (bundledVersion) fallbackPackageJson = { ...fallbackPackageJson, version: bundledVersion };
|
|
699
|
+
} catch (_) {
|
|
700
|
+
}
|
|
659
701
|
if (!await fs.pathExists(pkgPath)) {
|
|
660
|
-
await fs.writeJson(pkgPath,
|
|
702
|
+
await fs.writeJson(pkgPath, fallbackPackageJson, { spaces: 2 });
|
|
661
703
|
}
|
|
662
704
|
if (!await fs.pathExists(tsconfigPath)) {
|
|
663
705
|
await fs.writeJson(tsconfigPath, DEFAULT_SKILL_TSCONFIG, { spaces: 2 });
|
|
@@ -706,10 +748,16 @@ async function runInit(options) {
|
|
|
706
748
|
printBanner();
|
|
707
749
|
const fixedMessages = getFixedMessages();
|
|
708
750
|
let initIdentityInfo = null;
|
|
751
|
+
options = options || {};
|
|
752
|
+
options.server = cleanCliRawText(options.server || '');
|
|
753
|
+
options.upgrade = cleanCliRawText(options.upgrade || '');
|
|
754
|
+
options.download = cleanCliRawText(options.download || '');
|
|
755
|
+
options.invite = cleanCliRawText(options.invite || '');
|
|
756
|
+
options.project = cleanCliRawText(options.project || '');
|
|
757
|
+
options.code = cleanCliRawText(options.code || '');
|
|
709
758
|
|
|
710
759
|
const invite = decodeInviteToken(options.invite);
|
|
711
760
|
if (invite) {
|
|
712
|
-
options.server = options.server || invite.api;
|
|
713
761
|
options.upgrade = options.upgrade || invite.upgrade;
|
|
714
762
|
options.project = options.project || invite.projectId;
|
|
715
763
|
options.code = options.code || invite.code;
|
|
@@ -717,8 +765,8 @@ async function runInit(options) {
|
|
|
717
765
|
|
|
718
766
|
const workspacePath = resolveWorkspacePath(options.workspace);
|
|
719
767
|
const weiyuanPath = path.join(workspacePath, 'weiyuan');
|
|
720
|
-
const upgradeBaseUrl = options.upgrade || DEFAULT_CONFIG.upgradeBaseUrl;
|
|
721
|
-
const requestedDownloadUrl = options.download || DEFAULT_CONFIG.downloadUrl;
|
|
768
|
+
const upgradeBaseUrl = normalizeUpgradeUrl(options.upgrade || DEFAULT_CONFIG.upgradeBaseUrl) || DEFAULT_CONFIG.upgradeBaseUrl;
|
|
769
|
+
const requestedDownloadUrl = normalizeDownloadUrl(options.download || DEFAULT_CONFIG.downloadUrl);
|
|
722
770
|
const serverCandidates = resolveServerCandidates(options, invite);
|
|
723
771
|
let serverUrl = serverCandidates[0] || DEFAULT_CONFIG.serverUrl;
|
|
724
772
|
const force = options.force || false;
|
|
@@ -810,42 +858,57 @@ async function runInit(options) {
|
|
|
810
858
|
spinner = ora('检测到已有 weiyuan 运行目录,跳过下载与解压...').start();
|
|
811
859
|
spinner.succeed('已复用现有 weiyuan 目录');
|
|
812
860
|
} else {
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
861
|
+
const maxDownloadAttempts = 2;
|
|
862
|
+
let downloadCompleted = false;
|
|
863
|
+
let lastDownloadError = null;
|
|
864
|
+
for (let attempt = 1; attempt <= maxDownloadAttempts; attempt += 1) {
|
|
865
|
+
let source = null;
|
|
866
|
+
let zipPath = '';
|
|
867
|
+
try {
|
|
868
|
+
spinner = ora(attempt > 1 ? `重新解析下载包(第 ${attempt} 次)...` : '解析下载包...').start();
|
|
869
|
+
source = await resolvePackageSource({
|
|
870
|
+
downloadUrl: requestedDownloadUrl,
|
|
871
|
+
upgradeBaseUrl,
|
|
872
|
+
spinner
|
|
873
|
+
});
|
|
874
|
+
spinner.succeed(`下载源: ${source.downloadUrl}`);
|
|
875
|
+
printReleaseNotes(source.releaseNotes);
|
|
876
|
+
|
|
877
|
+
spinner = ora(attempt > 1 ? `重新下载 weiyuan skill(第 ${attempt} 次)...` : '下载 weiyuan skill...').start();
|
|
878
|
+
zipPath = path.join(weiyuanPath, source.zipFilename);
|
|
879
|
+
await fs.remove(zipPath).catch(() => {});
|
|
880
|
+
const downloadSuccess = await downloadFile(source.downloadUrl, zipPath, spinner, source.sha256 || '');
|
|
881
|
+
if (!downloadSuccess) throw new Error('下载失败');
|
|
882
|
+
spinner.succeed(`下载完成: ${source.zipFilename}`);
|
|
883
|
+
|
|
884
|
+
spinner = ora('解压文件...').start();
|
|
885
|
+
const extractSuccess = await extractZip(zipPath, weiyuanPath);
|
|
886
|
+
if (!extractSuccess) throw new Error('解压失败');
|
|
887
|
+
spinner.succeed('解压完成');
|
|
888
|
+
|
|
889
|
+
spinner = ora('清理临时文件...').start();
|
|
890
|
+
try {
|
|
891
|
+
await fs.remove(zipPath);
|
|
892
|
+
await persistReleaseNotes(weiyuanPath, source.releaseNotes || null);
|
|
893
|
+
spinner.succeed('清理完成');
|
|
894
|
+
} catch (error) {
|
|
895
|
+
spinner.warn('清理失败,可手动删除');
|
|
896
|
+
}
|
|
897
|
+
downloadCompleted = true;
|
|
898
|
+
lastDownloadError = null;
|
|
899
|
+
break;
|
|
900
|
+
} catch (error) {
|
|
901
|
+
lastDownloadError = error;
|
|
902
|
+
if (zipPath) await fs.remove(zipPath).catch(() => {});
|
|
903
|
+
const msg = String(error && error.message || error || '');
|
|
904
|
+
const canRetry = attempt < maxDownloadAttempts && msg.includes('skill_package_hash_mismatch');
|
|
905
|
+
if (spinner) {
|
|
906
|
+
spinner.warn(canRetry ? '下载包哈希校验未通过,正在重新获取最新版本后重试...' : `下载失败:${msg || 'unknown_error'}`);
|
|
907
|
+
}
|
|
908
|
+
if (!canRetry) throw error;
|
|
909
|
+
}
|
|
848
910
|
}
|
|
911
|
+
if (!downloadCompleted && lastDownloadError) throw lastDownloadError;
|
|
849
912
|
}
|
|
850
913
|
|
|
851
914
|
// 6. 准备 CLI 运行时
|
package/lib/downloader.js
CHANGED
|
@@ -12,6 +12,44 @@ function toZipFilename(downloadUrl) {
|
|
|
12
12
|
return parts[parts.length - 1] || 'weiyuan-openclaw-skill.zip';
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
function parseVersionFromZipFilename(zipFilename) {
|
|
16
|
+
const match = /-v(\d+\.\d+\.\d+)\.zip$/i.exec(String(zipFilename || '').trim());
|
|
17
|
+
return match ? match[1] : '';
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function buildFallbackReleaseNotes(version) {
|
|
21
|
+
const normalizedVersion = String(version || '').trim();
|
|
22
|
+
if (!normalizedVersion) return null;
|
|
23
|
+
return {
|
|
24
|
+
version: normalizedVersion,
|
|
25
|
+
summary: '本次更新已完成发布,请先阅读必读文件后再执行命令。',
|
|
26
|
+
changes: [
|
|
27
|
+
'已同步最新微元系统运行时与规则文件。',
|
|
28
|
+
'执行任何命令前,请先阅读 CHANGELOG_OPENCLAW.md、release-notes/latest.json、docs/OPENCLAW_WEIYUAN_CLI_QUICKSTART.md、docs/WEIYUAN_AGENT_RULES.md。'
|
|
29
|
+
],
|
|
30
|
+
releasedAt: new Date().toISOString()
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function normalizeReleaseNotesForResolvedVersion(releaseNotes, resolvedVersion) {
|
|
35
|
+
const version = String(resolvedVersion || '').trim();
|
|
36
|
+
if (!version) return releaseNotes || null;
|
|
37
|
+
const fallback = buildFallbackReleaseNotes(version);
|
|
38
|
+
if (!releaseNotes || typeof releaseNotes !== 'object') return fallback;
|
|
39
|
+
const notesVersion = String(releaseNotes.version || '').trim();
|
|
40
|
+
const summary = String(releaseNotes.summary || '').trim();
|
|
41
|
+
const changes = Array.isArray(releaseNotes.changes)
|
|
42
|
+
? releaseNotes.changes.map((item) => String(item || '').trim()).filter(Boolean)
|
|
43
|
+
: [];
|
|
44
|
+
return {
|
|
45
|
+
version,
|
|
46
|
+
summary: summary || fallback.summary,
|
|
47
|
+
changes: changes.length > 0 ? changes : fallback.changes,
|
|
48
|
+
releasedAt: String(releaseNotes.releasedAt || '').trim() || fallback.releasedAt,
|
|
49
|
+
sourceVersion: notesVersion || undefined
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
15
53
|
async function fetchLatestReleaseNotes(base, nonce) {
|
|
16
54
|
const notesUrl = `${base}/RELEASE_NOTES_LATEST.json?nocache=${nonce}`;
|
|
17
55
|
try {
|
|
@@ -34,7 +72,16 @@ async function fetchLatestReleaseNotes(base, nonce) {
|
|
|
34
72
|
|
|
35
73
|
async function resolvePackageSource({ downloadUrl, upgradeBaseUrl, spinner = null }) {
|
|
36
74
|
if (downloadUrl) {
|
|
37
|
-
|
|
75
|
+
const zipFilename = toZipFilename(downloadUrl);
|
|
76
|
+
const version = parseVersionFromZipFilename(zipFilename);
|
|
77
|
+
return {
|
|
78
|
+
downloadUrl,
|
|
79
|
+
zipFilename,
|
|
80
|
+
version: version || null,
|
|
81
|
+
sha256: '',
|
|
82
|
+
from: 'download_url',
|
|
83
|
+
releaseNotes: normalizeReleaseNotesForResolvedVersion(null, version)
|
|
84
|
+
};
|
|
38
85
|
}
|
|
39
86
|
|
|
40
87
|
const base = trimSlash(upgradeBaseUrl);
|
|
@@ -52,7 +99,14 @@ async function resolvePackageSource({ downloadUrl, upgradeBaseUrl, spinner = nul
|
|
|
52
99
|
if (spinner) {
|
|
53
100
|
spinner.text = `已解析最新版本: v${version}`;
|
|
54
101
|
}
|
|
55
|
-
return {
|
|
102
|
+
return {
|
|
103
|
+
downloadUrl: resolvedDownloadUrl,
|
|
104
|
+
zipFilename,
|
|
105
|
+
version,
|
|
106
|
+
sha256,
|
|
107
|
+
from: 'latest_json',
|
|
108
|
+
releaseNotes: normalizeReleaseNotesForResolvedVersion(releaseNotes, version)
|
|
109
|
+
};
|
|
56
110
|
}
|
|
57
111
|
} catch (_e) {
|
|
58
112
|
// fallback to txt
|
|
@@ -66,7 +120,14 @@ async function resolvePackageSource({ downloadUrl, upgradeBaseUrl, spinner = nul
|
|
|
66
120
|
if (spinner) {
|
|
67
121
|
spinner.text = `已解析最新版本: v${version}`;
|
|
68
122
|
}
|
|
69
|
-
return {
|
|
123
|
+
return {
|
|
124
|
+
downloadUrl: resolvedDownloadUrl,
|
|
125
|
+
zipFilename,
|
|
126
|
+
version,
|
|
127
|
+
sha256: '',
|
|
128
|
+
from: 'latest_version',
|
|
129
|
+
releaseNotes: normalizeReleaseNotesForResolvedVersion(releaseNotes, version)
|
|
130
|
+
};
|
|
70
131
|
}
|
|
71
132
|
|
|
72
133
|
async function downloadFile(url, outputPath, spinner = null, expectedSha256 = '') {
|
package/package.json
CHANGED
|
@@ -145,7 +145,7 @@ npm run weiyuan -- doc sync-check --project <projectId>
|
|
|
145
145
|
|
|
146
146
|
说明:
|
|
147
147
|
|
|
148
|
-
-
|
|
148
|
+
- 文件上传已恢复 CLI 直传能力,可使用 `doc upload --project <projectId> --file <路径>`;未显式提供 `--name` 或 `--type` 时会自动补默认值。
|
|
149
149
|
- 文件删除保留 CLI 能力,智能体 CLI 与小精灵 CLI 都支持删除文件。
|
|
150
150
|
|
|
151
151
|
Todo 行动清单:
|
|
@@ -803,7 +803,7 @@ async function repairIdentityForCurrentBinding(args: Argv): Promise<boolean> {
|
|
|
803
803
|
JSON.stringify(nextJoinedProjectIds) !== JSON.stringify(identity.joinedProjectIds ?? []) ||
|
|
804
804
|
nextCurrentProjectId !== String(identity.currentProjectId || "").trim() ||
|
|
805
805
|
JSON.stringify(nextProjectCursors) !== JSON.stringify(identity.projectCursors ?? {})
|
|
806
|
-
if (!changed) return
|
|
806
|
+
if (!changed) return true
|
|
807
807
|
await backupCurrentIdentityIfExists(filePath)
|
|
808
808
|
await writeIdentity(filePath, nextIdentity)
|
|
809
809
|
return true
|
|
@@ -5067,6 +5067,32 @@ function parseIssueAttachmentPaths(raw?: string): string[] {
|
|
|
5067
5067
|
.slice(0, ISSUE_ATTACHMENT_MAX_FILES)
|
|
5068
5068
|
}
|
|
5069
5069
|
|
|
5070
|
+
const DOC_UPLOAD_MIME_MAP: Record<string, string> = {
|
|
5071
|
+
".txt": "text/plain",
|
|
5072
|
+
".md": "text/markdown",
|
|
5073
|
+
".markdown": "text/markdown",
|
|
5074
|
+
".json": "application/json",
|
|
5075
|
+
".csv": "text/csv",
|
|
5076
|
+
".pdf": "application/pdf",
|
|
5077
|
+
".zip": "application/zip",
|
|
5078
|
+
".doc": "application/msword",
|
|
5079
|
+
".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
5080
|
+
".xls": "application/vnd.ms-excel",
|
|
5081
|
+
".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
5082
|
+
".ppt": "application/vnd.ms-powerpoint",
|
|
5083
|
+
".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
5084
|
+
".png": "image/png",
|
|
5085
|
+
".jpg": "image/jpeg",
|
|
5086
|
+
".jpeg": "image/jpeg",
|
|
5087
|
+
".gif": "image/gif",
|
|
5088
|
+
".webp": "image/webp",
|
|
5089
|
+
}
|
|
5090
|
+
|
|
5091
|
+
function guessDocMimeType(filePath: string): string {
|
|
5092
|
+
const ext = path.extname(String(filePath || "")).toLowerCase()
|
|
5093
|
+
return DOC_UPLOAD_MIME_MAP[ext] ?? "application/octet-stream"
|
|
5094
|
+
}
|
|
5095
|
+
|
|
5070
5096
|
async function loadIssueAttachments(attachmentPath?: string): Promise<Array<{ fileName: string; mimeType: string; contentBase64: string }>> {
|
|
5071
5097
|
const paths = parseIssueAttachmentPaths(attachmentPath)
|
|
5072
5098
|
if (!paths.length) return []
|
|
@@ -5760,8 +5786,93 @@ async function cmdDocList(args: Argv): Promise<void> {
|
|
|
5760
5786
|
}
|
|
5761
5787
|
|
|
5762
5788
|
async function cmdDocCreate(args: Argv): Promise<void> {
|
|
5763
|
-
|
|
5764
|
-
|
|
5789
|
+
const filePath = optString(args.flags, "identity") ?? DEFAULT_IDENTITY_PATH
|
|
5790
|
+
const identity = await readIdentity(filePath)
|
|
5791
|
+
const resolved = await resolveProjectRef(args.flags, identity)
|
|
5792
|
+
const interactive = Boolean(args.flags.interactive || args.flags.prompt)
|
|
5793
|
+
const sourceFileRaw = interactive
|
|
5794
|
+
? await askRequired("请选择要上传的文件路径: ", optString(args.flags, "file"))
|
|
5795
|
+
: mustString(args.flags, "file")
|
|
5796
|
+
const sourceFile = path.resolve(sourceFileRaw)
|
|
5797
|
+
let stat
|
|
5798
|
+
try {
|
|
5799
|
+
stat = await fs.stat(sourceFile)
|
|
5800
|
+
} catch {
|
|
5801
|
+
throw new Error(`invalid_doc_file_path: ${sourceFileRaw}`)
|
|
5802
|
+
}
|
|
5803
|
+
if (!stat.isFile()) throw new Error(`invalid_doc_file_path: ${sourceFileRaw}`)
|
|
5804
|
+
const bytes = await fs.readFile(sourceFile)
|
|
5805
|
+
const uploadNameFallback = path.basename(sourceFile)
|
|
5806
|
+
const uploadTypeFallback = guessDocMimeType(sourceFile)
|
|
5807
|
+
const docName = interactive
|
|
5808
|
+
? await askRequired("请输入上传后显示的文件名: ", optString(args.flags, "name") ?? optString(args.flags, "title") ?? uploadNameFallback)
|
|
5809
|
+
: String(optString(args.flags, "name") ?? optString(args.flags, "title") ?? uploadNameFallback).trim()
|
|
5810
|
+
const docType = interactive
|
|
5811
|
+
? await askRequired("请输入文件类型(可留空走自动识别): ", optString(args.flags, "type") ?? optString(args.flags, "docType") ?? uploadTypeFallback)
|
|
5812
|
+
: String(optString(args.flags, "type") ?? optString(args.flags, "docType") ?? uploadTypeFallback).trim()
|
|
5813
|
+
const fileSize = bytes.length
|
|
5814
|
+
if (fileSize > MAX_SHARED_DOC_FILE_BYTES) {
|
|
5815
|
+
throw new Error("doc_file_too_large: 单文件超过 10MB,请压缩后重试。")
|
|
5816
|
+
}
|
|
5817
|
+
const contentHash = sha256Hex(bytes)
|
|
5818
|
+
const storageType = optString(args.flags, "storageType")
|
|
5819
|
+
const storageProvider = optString(args.flags, "storageProvider")
|
|
5820
|
+
const storagePath = optString(args.flags, "storagePath") ?? sourceFile
|
|
5821
|
+
const tags = String(optString(args.flags, "tags") ?? "")
|
|
5822
|
+
.split(",")
|
|
5823
|
+
.map((x) => x.trim())
|
|
5824
|
+
.filter(Boolean)
|
|
5825
|
+
const relatedTaskIds = String(optString(args.flags, "tasks") ?? optString(args.flags, "task") ?? "")
|
|
5826
|
+
.split(",")
|
|
5827
|
+
.map((x) => x.trim())
|
|
5828
|
+
.filter(Boolean)
|
|
5829
|
+
const version = optString(args.flags, "version") ?? "v1"
|
|
5830
|
+
const description = optString(args.flags, "desc") ?? optString(args.flags, "description")
|
|
5831
|
+
const changeSummary = optString(args.flags, "summary")
|
|
5832
|
+
const idempotencyKey = createRequestId(`doc_upload_${path.basename(sourceFile)}`)
|
|
5833
|
+
const { status, json } = await signedRequest({
|
|
5834
|
+
serverBaseUrl: identity.serverBaseUrl,
|
|
5835
|
+
method: "POST",
|
|
5836
|
+
pathWithQuery: `/v1/projects/${resolved.projectId}/docs`,
|
|
5837
|
+
lobsterId: identity.lobsterId,
|
|
5838
|
+
secretKeyBase64: identity.secretKeyBase64,
|
|
5839
|
+
idempotencyKey,
|
|
5840
|
+
body: {
|
|
5841
|
+
docName,
|
|
5842
|
+
docType,
|
|
5843
|
+
fileSize,
|
|
5844
|
+
contentHash,
|
|
5845
|
+
contentBase64: bytes.toString("base64"),
|
|
5846
|
+
storageLocation: { type: storageType ?? undefined, provider: storageProvider, path: storagePath },
|
|
5847
|
+
version,
|
|
5848
|
+
relatedTaskIds,
|
|
5849
|
+
tags,
|
|
5850
|
+
description,
|
|
5851
|
+
changeSummary,
|
|
5852
|
+
},
|
|
5853
|
+
})
|
|
5854
|
+
if (status !== 200) {
|
|
5855
|
+
const msg = String((json as any)?.msg ?? (json as any)?.message ?? "")
|
|
5856
|
+
if (status === 409 && msg.includes("storage_quota_exceeded")) {
|
|
5857
|
+
throw new Error("storage_quota_exceeded: 存储空间已达上限,请联系 founder 升级容量后再上传。")
|
|
5858
|
+
}
|
|
5859
|
+
if (status === 409 && msg === "invalid_storage_profile") {
|
|
5860
|
+
throw new Error("invalid_storage_profile: 当前项目存储配置无效,请联系 founder 检查项目存储设置。")
|
|
5861
|
+
}
|
|
5862
|
+
throw new Error(JSON.stringify(json))
|
|
5863
|
+
}
|
|
5864
|
+
print({
|
|
5865
|
+
ok: true,
|
|
5866
|
+
projectId: resolved.projectId,
|
|
5867
|
+
projectName: resolved.projectName ?? "",
|
|
5868
|
+
docName,
|
|
5869
|
+
docType,
|
|
5870
|
+
sourceFile,
|
|
5871
|
+
fileSize,
|
|
5872
|
+
contentHash,
|
|
5873
|
+
uploadedAt: Date.now(),
|
|
5874
|
+
...(json.data && typeof json.data === "object" ? (json.data as Record<string, unknown>) : {}),
|
|
5875
|
+
})
|
|
5765
5876
|
}
|
|
5766
5877
|
|
|
5767
5878
|
async function cmdDocDetail(args: Argv): Promise<void> {
|
|
@@ -18,7 +18,9 @@ function toUserError(raw: string): string {
|
|
|
18
18
|
if (msg.includes("interactive_input_required")) return "必填内容不能为空,请补充参数后重试。"
|
|
19
19
|
if (msg.includes("missing_project_name")) return "项目名不能为空,请通过 --name 提供有效项目名。"
|
|
20
20
|
if (msg.includes("project_name_too_long")) return "项目名不能超过80个字符,请缩短后重试。"
|
|
21
|
-
if (msg.includes("doc_upload_board_only")) return "
|
|
21
|
+
if (msg.includes("doc_upload_board_only")) return "当前运行时版本过旧,文件上传仍被禁用。请先更新微元系统后再试。"
|
|
22
|
+
if (msg.includes("invalid_doc_file_path")) return "上传文件路径不存在,或该路径不是一个可读取的文件。请检查 --file 后重试。"
|
|
23
|
+
if (msg.includes("invalid_storage_profile")) return "当前项目的存储配置异常,暂时无法上传文件。请联系项目 founder 检查存储设置。"
|
|
22
24
|
if (msg.includes("invalid_project_name")) return "项目名仅支持中文/英文/数字/空格及 -_.()·,且不能包含HTML标签。"
|
|
23
25
|
if (msg.includes("task_title_contains_html")) return "任务标题不能包含HTML标签(如 <script>)。"
|
|
24
26
|
if (msg.includes("task_title_too_long")) return "任务标题不能超过120个字符,请缩短后重试。"
|
|
@@ -362,6 +362,90 @@ function extractNicknameFromText(text: string): string | undefined {
|
|
|
362
362
|
return v || undefined
|
|
363
363
|
}
|
|
364
364
|
|
|
365
|
+
function extractMemberProfileFieldFromText(
|
|
366
|
+
text: string,
|
|
367
|
+
field: "roleTitle" | "specialties" | "experienceSummary" | "strengthsSummary" | "taskPreferenceTags"
|
|
368
|
+
): string | undefined {
|
|
369
|
+
const patterns: RegExp[] =
|
|
370
|
+
field === "roleTitle"
|
|
371
|
+
? [
|
|
372
|
+
/(?:设置|修改|更改|更新)(?:我的)?(?:团队)?角色(?:与能力)?(?:为|叫|成)?\s*[“"']?([^”"'\n]+)[”"']?$/u,
|
|
373
|
+
/(?:我的)?(?:团队)?角色(?:是|为|叫|改成|改为)\s*[“"']?([^”"'\n]+)[”"']?$/u,
|
|
374
|
+
]
|
|
375
|
+
: field === "specialties"
|
|
376
|
+
? [
|
|
377
|
+
/(?:设置|修改|更改|更新)(?:我的)?(?:擅长方向|擅长|专长|特长方向)(?:为|成)?\s*[“"']?([^”"'\n]+)[”"']?$/u,
|
|
378
|
+
/(?:我的)?(?:擅长方向|擅长|专长|特长方向)(?:是|为|改成|改为)\s*[“"']?([^”"'\n]+)[”"']?$/u,
|
|
379
|
+
]
|
|
380
|
+
: field === "experienceSummary"
|
|
381
|
+
? [
|
|
382
|
+
/(?:设置|修改|更改|更新)(?:我的)?(?:经历概述|经历简介|经验概述|经验简介|经历)(?:为|成)?\s*[“"']?([^”"'\n]+)[”"']?$/u,
|
|
383
|
+
/(?:我的)?(?:经历概述|经历简介|经验概述|经验简介|经历)(?:是|为|改成|改为)\s*[“"']?([^”"'\n]+)[”"']?$/u,
|
|
384
|
+
]
|
|
385
|
+
: field === "strengthsSummary"
|
|
386
|
+
? [
|
|
387
|
+
/(?:设置|修改|更改|更新)(?:我的)?(?:特长概述|优势概述|优势简介|特长简介|特长)(?:为|成)?\s*[“"']?([^”"'\n]+)[”"']?$/u,
|
|
388
|
+
/(?:我的)?(?:特长概述|优势概述|优势简介|特长简介|特长)(?:是|为|改成|改为)\s*[“"']?([^”"'\n]+)[”"']?$/u,
|
|
389
|
+
]
|
|
390
|
+
: [
|
|
391
|
+
/(?:设置|修改|更改|更新)(?:我的)?(?:偏好任务标签|任务偏好标签|偏好标签|任务标签)(?:为|成)?\s*[“"']?([^”"'\n]+)[”"']?$/u,
|
|
392
|
+
/(?:我的)?(?:偏好任务标签|任务偏好标签|偏好标签|任务标签)(?:是|为|改成|改为)\s*[“"']?([^”"'\n]+)[”"']?$/u,
|
|
393
|
+
]
|
|
394
|
+
for (const pattern of patterns) {
|
|
395
|
+
const value = String(text.match(pattern)?.[1] ?? "").trim()
|
|
396
|
+
if (value) return value
|
|
397
|
+
}
|
|
398
|
+
return undefined
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
function hasMemberProfileCardIntent(text: string): boolean {
|
|
402
|
+
const compact = text.toLowerCase().replace(/\s+/g, "")
|
|
403
|
+
return (
|
|
404
|
+
compact.includes("成员角色卡片") ||
|
|
405
|
+
compact.includes("角色卡片") ||
|
|
406
|
+
compact.includes("成员卡片") ||
|
|
407
|
+
compact.includes("编辑成员信息") ||
|
|
408
|
+
compact.includes("修改成员信息") ||
|
|
409
|
+
compact.includes("设置角色卡片") ||
|
|
410
|
+
compact.includes("角色与能力") ||
|
|
411
|
+
compact.includes("我的角色") ||
|
|
412
|
+
compact.includes("修改角色") ||
|
|
413
|
+
compact.includes("更新角色") ||
|
|
414
|
+
compact.includes("团队角色") ||
|
|
415
|
+
compact.includes("擅长方向") ||
|
|
416
|
+
compact.includes("我的擅长") ||
|
|
417
|
+
compact.includes("我的专长") ||
|
|
418
|
+
compact.includes("经历概述") ||
|
|
419
|
+
compact.includes("经历简介") ||
|
|
420
|
+
compact.includes("经验概述") ||
|
|
421
|
+
compact.includes("我的经历") ||
|
|
422
|
+
compact.includes("特长概述") ||
|
|
423
|
+
compact.includes("优势概述") ||
|
|
424
|
+
compact.includes("我的特长") ||
|
|
425
|
+
compact.includes("我的优势") ||
|
|
426
|
+
compact.includes("偏好任务标签") ||
|
|
427
|
+
compact.includes("任务偏好标签")
|
|
428
|
+
)
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
function extractMemberRemarkIntent(text: string): { target?: string; remark?: string; clear?: boolean } | undefined {
|
|
432
|
+
const trimmed = String(text || "").trim()
|
|
433
|
+
if (!trimmed) return undefined
|
|
434
|
+
const clearMatch =
|
|
435
|
+
trimmed.match(/(?:清空|删除|移除|去掉)(?:我对)?\s*([^,,\s]+)\s*(?:的)?备注$/u) ??
|
|
436
|
+
trimmed.match(/(?:把)?\s*([^,,\s]+)\s*(?:的)?备注(?:清空|删除|移除|去掉)$/u)
|
|
437
|
+
if (clearMatch?.[1]) {
|
|
438
|
+
return { target: clearMatch[1].trim(), clear: true, remark: "-" }
|
|
439
|
+
}
|
|
440
|
+
const saveMatch =
|
|
441
|
+
trimmed.match(/(?:给|帮我给|把)?\s*([^,,\s]+)\s*(?:的)?备注(?:为|成)?\s*([^\n]+)$/u) ??
|
|
442
|
+
trimmed.match(/(?:把)?\s*([^,,\s]+)\s*(?:备注为|备注成)\s*([^\n]+)$/u)
|
|
443
|
+
if (saveMatch?.[1]) {
|
|
444
|
+
return { target: saveMatch[1].trim(), remark: String(saveMatch[2] ?? "").trim() }
|
|
445
|
+
}
|
|
446
|
+
return undefined
|
|
447
|
+
}
|
|
448
|
+
|
|
365
449
|
function extractProjectNameForCreate(text: string): string | undefined {
|
|
366
450
|
const m =
|
|
367
451
|
text.match(/(?:创建|新建)(?:一个|个)?[“"'《【((]?\s*([^”"'》】))\n]+?项目)\s*(?:吧|呀|呢|的)?$/) ??
|
|
@@ -1761,22 +1845,33 @@ function fromText(input: SkillInput): string[] {
|
|
|
1761
1845
|
if (!nickname) return ["member-profile-edit", "--identity", identity, "--project", input.projectId]
|
|
1762
1846
|
return ["member-profile-set", "--identity", identity, "--project", input.projectId, "--nickname", nickname]
|
|
1763
1847
|
}
|
|
1764
|
-
if (
|
|
1765
|
-
compact.includes("成员角色卡片") ||
|
|
1766
|
-
compact.includes("角色卡片") ||
|
|
1767
|
-
compact.includes("成员卡片") ||
|
|
1768
|
-
compact.includes("编辑成员信息") ||
|
|
1769
|
-
compact.includes("修改成员信息") ||
|
|
1770
|
-
compact.includes("设置角色卡片")
|
|
1771
|
-
) {
|
|
1848
|
+
if (hasMemberProfileCardIntent(text)) {
|
|
1772
1849
|
if (!input.projectId) return ["context", "candidates", "--identity", identity]
|
|
1773
|
-
|
|
1850
|
+
const roleTitle = extractMemberProfileFieldFromText(text, "roleTitle")
|
|
1851
|
+
const specialties = extractMemberProfileFieldFromText(text, "specialties")
|
|
1852
|
+
const experienceSummary = extractMemberProfileFieldFromText(text, "experienceSummary")
|
|
1853
|
+
const strengthsSummary = extractMemberProfileFieldFromText(text, "strengthsSummary")
|
|
1854
|
+
const taskPreferenceTags = extractMemberProfileFieldFromText(text, "taskPreferenceTags")
|
|
1855
|
+
const argv = ["member-profile-edit", "--identity", identity, "--project", input.projectId]
|
|
1856
|
+
if (roleTitle) argv.push("--roleTitle", roleTitle)
|
|
1857
|
+
if (specialties) argv.push("--specialties", specialties)
|
|
1858
|
+
if (experienceSummary) argv.push("--experienceSummary", experienceSummary)
|
|
1859
|
+
if (strengthsSummary) argv.push("--strengthsSummary", strengthsSummary)
|
|
1860
|
+
if (taskPreferenceTags) argv.push("--taskPreferenceTags", taskPreferenceTags)
|
|
1861
|
+
return argv
|
|
1774
1862
|
}
|
|
1775
|
-
if (
|
|
1863
|
+
if (
|
|
1864
|
+
compact.includes("成员备注") ||
|
|
1865
|
+
compact.includes("备注成员") ||
|
|
1866
|
+
compact.includes("给成员备注") ||
|
|
1867
|
+
compact.includes("修改备注") ||
|
|
1868
|
+
compact.includes("清空备注") ||
|
|
1869
|
+
compact.includes("删除备注")
|
|
1870
|
+
) {
|
|
1776
1871
|
if (!input.projectId) return ["context", "candidates", "--identity", identity]
|
|
1777
|
-
const
|
|
1778
|
-
const target = (
|
|
1779
|
-
const remark = (
|
|
1872
|
+
const remarkIntent = extractMemberRemarkIntent(text)
|
|
1873
|
+
const target = String(remarkIntent?.target ?? "").trim()
|
|
1874
|
+
const remark = String(remarkIntent?.remark ?? "").trim()
|
|
1780
1875
|
if (target && remark) {
|
|
1781
1876
|
return ["panel", "member-remark", "--identity", identity, "--project", input.projectId, "--target", target, "--remark", remark]
|
|
1782
1877
|
}
|
|
@@ -2385,8 +2480,20 @@ function fromText(input: SkillInput): string[] {
|
|
|
2385
2480
|
}
|
|
2386
2481
|
if (lower.includes("上传文件") || lower.includes("上传文档") || lower.includes("新增文档")) {
|
|
2387
2482
|
if (!input.projectId) return ["context", "candidates", "--identity", identity]
|
|
2388
|
-
if (!input.file
|
|
2389
|
-
return [
|
|
2483
|
+
if (!input.file) return ["doc", "upload", "--identity", identity, "--project", input.projectId, "--interactive"]
|
|
2484
|
+
return [
|
|
2485
|
+
"doc",
|
|
2486
|
+
"upload",
|
|
2487
|
+
"--identity",
|
|
2488
|
+
identity,
|
|
2489
|
+
"--project",
|
|
2490
|
+
input.projectId,
|
|
2491
|
+
"--file",
|
|
2492
|
+
input.file,
|
|
2493
|
+
...(input.title ? ["--name", input.title] : []),
|
|
2494
|
+
...(input.docType ? ["--type", input.docType] : []),
|
|
2495
|
+
...(input.desc ? ["--desc", input.desc] : []),
|
|
2496
|
+
]
|
|
2390
2497
|
}
|
|
2391
2498
|
if (lower.includes("老规矩") || lower.includes("上次那套")) {
|
|
2392
2499
|
if (input.projectId) return ["capsule", "recommend", "--identity", identity, "--project", input.projectId, "--query", input.query ?? ""]
|
|
@@ -2659,8 +2766,8 @@ function fromAction(input: SkillInput): string[] {
|
|
|
2659
2766
|
]
|
|
2660
2767
|
case "doc.upload":
|
|
2661
2768
|
if (!input.projectId) throw new Error("missing_projectId")
|
|
2662
|
-
if (!input.file
|
|
2663
|
-
return ["doc", "upload", "--identity", identity, "--project", input.projectId, "--interactive", ...(input.title ? ["--name", input.title] : []), ...(input.docType ? ["--type", input.docType] : [])
|
|
2769
|
+
if (!input.file) {
|
|
2770
|
+
return ["doc", "upload", "--identity", identity, "--project", input.projectId, "--interactive", ...(input.title ? ["--name", input.title] : []), ...(input.docType ? ["--type", input.docType] : [])]
|
|
2664
2771
|
}
|
|
2665
2772
|
return [
|
|
2666
2773
|
"doc",
|
|
@@ -2669,12 +2776,10 @@ function fromAction(input: SkillInput): string[] {
|
|
|
2669
2776
|
identity,
|
|
2670
2777
|
"--project",
|
|
2671
2778
|
input.projectId,
|
|
2672
|
-
"--name",
|
|
2673
|
-
input.title,
|
|
2674
|
-
"--type",
|
|
2675
|
-
input.docType,
|
|
2676
2779
|
"--file",
|
|
2677
2780
|
input.file,
|
|
2781
|
+
...(input.title ? ["--name", input.title] : []),
|
|
2782
|
+
...(input.docType ? ["--type", input.docType] : []),
|
|
2678
2783
|
...(input.desc ? ["--desc", input.desc] : []),
|
|
2679
2784
|
]
|
|
2680
2785
|
case "doc.delete":
|