openclaw-weiyuan-init 1.0.130 → 1.0.131

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openclaw-weiyuan-init",
3
- "version": "1.0.130",
3
+ "version": "1.0.131",
4
4
  "description": "OpenClaw Weiyuan Skill 一键初始化工具",
5
5
  "main": "bin/cli.js",
6
6
  "bin": {
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "weiyuan-openclaw-skill",
3
- "displayName": "微元协作 Skill",
4
- "version": "0.3.772",
5
- "description": "通过结构化 action 或自然语言 text 调用微元云服务。",
3
+ "displayName": "微元 OpenClaw Skill",
4
+ "version": "0.3.1049",
5
+ "description": "微元系统技能入口,支持 action text 两种调用方式。",
6
6
  "entry": {
7
7
  "type": "command",
8
8
  "command": "npm run weiyuan:skill"
@@ -19,6 +19,7 @@
19
19
  "join",
20
20
  "project.list",
21
21
  "project.rename",
22
+ "project.link_task",
22
23
  "project.delete",
23
24
  "project.storage",
24
25
  "project.storage_set",
@@ -42,6 +43,9 @@
42
43
  "task.update",
43
44
  "task.take",
44
45
  "task.delete",
46
+ "task.link_project",
47
+ "task.unlink_project",
48
+ "task.list_linked_project",
45
49
  "task.submit",
46
50
  "task.evolution_list",
47
51
  "task.evolution_add",
@@ -141,4 +145,4 @@
141
145
  "rawError": ""
142
146
  }
143
147
  }
144
- }
148
+ }
@@ -19,6 +19,8 @@
19
19
  "join",
20
20
  "project.list",
21
21
  "project.rename",
22
+ "project.link_task",
23
+ "project.link-task",
22
24
  "project.delete",
23
25
  "project.storage",
24
26
  "project.storage_set",
@@ -52,6 +54,12 @@
52
54
  "task.update",
53
55
  "task.take",
54
56
  "task.delete",
57
+ "task.link_project",
58
+ "task.link-project",
59
+ "task.unlink_project",
60
+ "task.unlink-project",
61
+ "task.list_linked_project",
62
+ "task.list-linked-project",
55
63
  "task.submit",
56
64
  "task.evolution_list",
57
65
  "task.evolution_add",
@@ -154,6 +162,10 @@
154
162
  "identity": { "type": "string" },
155
163
  "projectId": { "type": "string" },
156
164
  "taskId": { "type": "string" },
165
+ "linkedProjectId": { "type": "string" },
166
+ "linkedProjectIds": { "type": "string" },
167
+ "targetProjectId": { "type": "string" },
168
+ "mode": { "type": "string" },
157
169
  "docId": { "type": "string" },
158
170
  "eventId": { "type": "string" },
159
171
  "backupId": { "type": "string" },
@@ -1,8 +1,9 @@
1
1
  import { runCli } from "./cli"
2
2
  import { CAPABILITY_ACTION } from "./capabilityActions"
3
- import { normalizeServerBaseUrl } from "./weiyuanFile"
3
+ import { normalizeServerBaseUrl, writeIdentity } from "./weiyuanFile"
4
4
  import { DELETE_DOC_CONFIRM_NOTICE, DELETE_TASK_CONFIRM_NOTICE } from "./copyStandards"
5
5
  import { parseInteractiveTaskStatusFromText } from "./taskStandards"
6
+ import type { WeiyuanIdentityFileV1 } from "./types"
6
7
  import * as fs from "node:fs/promises"
7
8
  import * as nodePath from "node:path"
8
9
  import { createHash } from "node:crypto"
@@ -15,6 +16,10 @@ type SkillInput = {
15
16
  identity?: string
16
17
  projectId?: string
17
18
  taskId?: string
19
+ linkedProjectId?: string
20
+ linkedProjectIds?: string
21
+ targetProjectId?: string
22
+ mode?: string
18
23
  to?: string
19
24
  targetLobsterId?: string
20
25
  memberId?: string
@@ -102,6 +107,17 @@ type MicroContextState = {
102
107
  lastObservedSkillVersion?: string
103
108
  }
104
109
 
110
+ type SongXingManifest = {
111
+ version: number
112
+ partnerCode: string
113
+ accountSeries: string
114
+ lobsterId?: string
115
+ accountId?: string
116
+ deliveryCode: string
117
+ serverBaseUrl?: string
118
+ runtimeBindPath?: string
119
+ }
120
+
105
121
  type PendingFlowState =
106
122
  | { kind: "member_profile_rename"; step: "await_project" }
107
123
  | { kind: "member_profile_rename"; step: "await_nickname"; projectId: string; projectName?: string }
@@ -568,6 +584,96 @@ function getExpressionPolicyPath(identityPath: string): string {
568
584
  return nodePath.join(nodePath.dirname(abs), ".weiyuan-expression-policy.json")
569
585
  }
570
586
 
587
+ function getSongXingManifestCandidatePaths(identityPath: string): string[] {
588
+ const abs = nodePath.resolve(identityPath)
589
+ const identityDir = nodePath.dirname(abs)
590
+ return [
591
+ nodePath.join(identityDir, "songxing.manifest.json"),
592
+ nodePath.join(identityDir, "workspace-weiyuan", "songxing.manifest.json"),
593
+ nodePath.resolve(process.cwd(), "songxing.manifest.json"),
594
+ nodePath.resolve(process.cwd(), "workspace-weiyuan", "songxing.manifest.json"),
595
+ ]
596
+ }
597
+
598
+ async function readSongXingManifest(identityPath: string): Promise<SongXingManifest | undefined> {
599
+ for (const p of getSongXingManifestCandidatePaths(identityPath)) {
600
+ try {
601
+ const raw = await fs.readFile(p, "utf8")
602
+ const parsed = JSON.parse(raw) as Partial<SongXingManifest>
603
+ if (!parsed || typeof parsed !== "object") continue
604
+ const partnerCode = String(parsed.partnerCode ?? "").trim().toLowerCase()
605
+ const accountSeries = String(parsed.accountSeries ?? "").trim().toLowerCase()
606
+ const deliveryCode = String(parsed.deliveryCode ?? "").trim()
607
+ if (partnerCode !== "songxing" || accountSeries !== "songxing" || !deliveryCode) continue
608
+ return {
609
+ version: Number(parsed.version ?? 1),
610
+ partnerCode,
611
+ accountSeries,
612
+ lobsterId: String(parsed.lobsterId ?? "").trim() || undefined,
613
+ accountId: String(parsed.accountId ?? "").trim() || undefined,
614
+ deliveryCode,
615
+ serverBaseUrl: String(parsed.serverBaseUrl ?? "").trim() || undefined,
616
+ runtimeBindPath: String(parsed.runtimeBindPath ?? "").trim() || undefined,
617
+ }
618
+ } catch {
619
+ }
620
+ }
621
+ return undefined
622
+ }
623
+
624
+ function buildSongXingWorkspaceFingerprint(identityPath: string): string {
625
+ const abs = nodePath.resolve(identityPath)
626
+ return `${process.cwd()}|${nodePath.dirname(abs)}`
627
+ }
628
+
629
+ async function ensureSongXingBindingIfNeeded(
630
+ identityPath: string,
631
+ input: SkillInput,
632
+ text: string,
633
+ rules: MicroRulesConfig,
634
+ state: MicroContextState,
635
+ ): Promise<void> {
636
+ const manifest = await readSongXingManifest(identityPath)
637
+ if (!manifest) return
638
+ const shouldEnsure =
639
+ Boolean(input.action) ||
640
+ isMicroSceneText(text, rules) ||
641
+ shouldPreferMicroByContext(text, state) ||
642
+ /(songxing|微元|驾驶舱|项目|任务|待办|看板)/i.test(text)
643
+ if (!shouldEnsure) return
644
+ const serverBaseUrl = normalizeServerBaseUrl(String(manifest.serverBaseUrl || "https://api.magon.com.cn/api"))
645
+ const runtimePath = String(manifest.runtimeBindPath || "/v1/songxing/runtime/ensure-binding").trim() || "/v1/songxing/runtime/ensure-binding"
646
+ const res = await fetch(`${serverBaseUrl}${runtimePath}`, {
647
+ method: "POST",
648
+ headers: { "Content-Type": "application/json" },
649
+ body: JSON.stringify({
650
+ manifest,
651
+ workspaceFingerprint: buildSongXingWorkspaceFingerprint(identityPath),
652
+ workspaceLabel: process.cwd(),
653
+ }),
654
+ })
655
+ const raw = await res.text()
656
+ let json: any
657
+ try {
658
+ json = JSON.parse(raw)
659
+ } catch {
660
+ json = { msg: raw }
661
+ }
662
+ if (!res.ok) {
663
+ throw new Error(String(json?.msg || json?.message || "songxing_runtime_bind_failed"))
664
+ }
665
+ const payload = json?.data && typeof json.data === "object" ? json.data : json
666
+ const identityRaw = payload?.identity
667
+ if (!identityRaw || typeof identityRaw !== "object" || Array.isArray(identityRaw)) {
668
+ throw new Error("songxing_identity_payload_missing")
669
+ }
670
+ const nextIdentity = {
671
+ ...(identityRaw as Record<string, unknown>),
672
+ serverBaseUrl: normalizeServerBaseUrl(String((identityRaw as any).serverBaseUrl || serverBaseUrl)),
673
+ } as WeiyuanIdentityFileV1
674
+ await writeIdentity(identityPath, nextIdentity)
675
+ }
676
+
571
677
  function mergeRules(raw: unknown): MicroRulesConfig {
572
678
  const src = (raw && typeof raw === "object" ? (raw as Record<string, unknown>) : {}) as Record<string, unknown>
573
679
  const arr = (v: unknown, fallback: string[]): string[] =>
@@ -1004,7 +1110,7 @@ async function enforceLinkContractByCommand(raw: unknown, argv: string[], identi
1004
1110
  const identityRaw = await fs.readFile(identityPath, "utf8")
1005
1111
  const identity = JSON.parse(identityRaw) as Record<string, unknown>
1006
1112
  const serverBase = normalizeServerBaseUrl(String(identity.serverBaseUrl ?? ""))
1007
- const lobsterId = String(identity.lobsterId ?? "")
1113
+ const lobsterId = String(identity.effectiveLobsterId ?? identity.lobsterId ?? "")
1008
1114
  if (serverBase && lobsterId) {
1009
1115
  const u = new URL(serverBase)
1010
1116
  const dashboardLink = `${u.origin}/api/dashboard-web?lobsterId=${encodeURIComponent(lobsterId)}`
@@ -1053,6 +1159,7 @@ const DEFAULT_REPLY_TEMPLATES: ReplyTemplateMap = {
1053
1159
  }
1054
1160
 
1055
1161
  const WEIYUAN_OFFICIAL_WEBSITE = "https://www.magon.com.cn/"
1162
+ const WEIYUAN_AGENT_SKILL_DOWNLOAD_URL = "https://api.magon.com.cn/api/downloads/weiyuan-agent-cli-skill.md"
1056
1163
 
1057
1164
  function withOfficialWebsiteIfLinkPresent(obj: Record<string, unknown>, message: string): { message: string; officialWebsite: string } | null {
1058
1165
  const hasDashboard = typeof obj.dashboardLink === "string" && obj.dashboardLink.trim().length > 0
@@ -1130,10 +1237,11 @@ function buildMandatoryDisplay(actionKey: string | undefined, core: unknown): st
1130
1237
  const dashboardLink = typeof c.dashboardLink === "string" ? c.dashboardLink.trim() : (typeof c.dashboardUrl === "string" ? c.dashboardUrl.trim() : "")
1131
1238
  const bindPhoneUrl = typeof c.bindPhoneUrl === "string" ? c.bindPhoneUrl.trim() : ""
1132
1239
  const loginUrl = typeof c.loginUrl === "string" ? c.loginUrl.trim() : ""
1240
+ const skillDownloadUrl = typeof c.skillDownloadUrl === "string" ? c.skillDownloadUrl.trim() : WEIYUAN_AGENT_SKILL_DOWNLOAD_URL
1133
1241
  const officialWebsite = typeof c.officialWebsite === "string"
1134
1242
  ? c.officialWebsite.trim()
1135
1243
  : (typeof c.officialSiteUrl === "string" ? c.officialSiteUrl.trim() : WEIYUAN_OFFICIAL_WEBSITE)
1136
- if (account && (dashboardLink || bindPhoneUrl || loginUrl || officialWebsite)) {
1244
+ if (account && (dashboardLink || bindPhoneUrl || loginUrl || officialWebsite || skillDownloadUrl)) {
1137
1245
  return [
1138
1246
  `### 🌀 微元系统账号入口 (不可修改)(数据时间:${time} | Trace_ID:${traceId})`,
1139
1247
  "| 项目 | 内容 |",
@@ -1143,8 +1251,10 @@ function buildMandatoryDisplay(actionKey: string | undefined, core: unknown): st
1143
1251
  `| 手机号绑定 | ${bindPhoneUrl || "未返回"} |`,
1144
1252
  `| 登录页面 | ${loginUrl || "未返回"} |`,
1145
1253
  `| 官方网站 | ${officialWebsite || WEIYUAN_OFFICIAL_WEBSITE} |`,
1254
+ `| Skill 下载 | ${skillDownloadUrl || WEIYUAN_AGENT_SKILL_DOWNLOAD_URL} |`,
1146
1255
  "",
1147
1256
  `- **重点提醒**:个人驾驶舱链接是重点入口,请立即保存:${dashboardLink || "当前未返回,请重新获取一次加入成功结果"}`,
1257
+ `- **Skill 下载**:如果你要把微元系统能力导入到 workbuddy、豆包等智能体,请下载并导入:${skillDownloadUrl || WEIYUAN_AGENT_SKILL_DOWNLOAD_URL}`,
1148
1258
  "-----------------------------------",
1149
1259
  ].join("\n")
1150
1260
  }
@@ -1230,8 +1340,155 @@ function pickInviteCode(core: unknown): string {
1230
1340
  return ""
1231
1341
  }
1232
1342
 
1343
+ function normalizeProjectQueryText(raw: unknown): string {
1344
+ return String(raw ?? "")
1345
+ .trim()
1346
+ .toLowerCase()
1347
+ .replace(/[“”"'`]/g, "")
1348
+ .replace(/\s+/g, "")
1349
+ .replace(/[,。!?!?::、()()【】\[\]\-—_]/g, "")
1350
+ }
1351
+
1352
+ function isProjectTaskQueryText(text: string): boolean {
1353
+ const compact = normalizeProjectQueryText(text)
1354
+ return /(我的任务|我有哪些任务|我有什么任务|看看任务|看看我的任务|查任务|任务列表|任务情况)/i.test(compact)
1355
+ }
1356
+
1357
+ function isProjectMemberQueryText(text: string): boolean {
1358
+ const compact = normalizeProjectQueryText(text)
1359
+ return /(成员列表|成员情况|项目成员|看看成员|查看成员|人员情况|人员列表)/i.test(compact)
1360
+ }
1361
+
1362
+ function summarizeMemberRole(roleRaw: unknown): string {
1363
+ const role = String(roleRaw ?? "").trim().toLowerCase()
1364
+ if (role === "founder") return "创始人"
1365
+ if (role === "leader") return "负责人"
1366
+ return "成员"
1367
+ }
1368
+
1369
+ function buildDeterministicTaskListMessage(core: unknown): string | undefined {
1370
+ const c = asRecord(core)
1371
+ const tasks = Array.isArray(c.executionTasks)
1372
+ ? (c.executionTasks as Array<Record<string, unknown>>)
1373
+ : Array.isArray(c.tasks)
1374
+ ? (c.tasks as Array<Record<string, unknown>>)
1375
+ : []
1376
+ if (!tasks.length) return undefined
1377
+ const projectName = pickProjectName(c)
1378
+ const mine = tasks.filter((t) => t.claimedByMe === true || t.mine === true)
1379
+ const focus = tasks.filter((t) => String(t.taskBoardBucket ?? "").trim() === "focus")
1380
+ const waiting = tasks.filter((t) => String(t.taskBoardBucket ?? "").trim() === "waiting")
1381
+ const unclaimed = tasks.filter((t) => String(t.taskBoardBucket ?? "").trim() === "unclaimed")
1382
+ const done = tasks.filter((t) => String(t.taskBoardBucket ?? "").trim() === "done")
1383
+ const mineLines = mine.map((t, idx) => {
1384
+ const title = String(t.title ?? t.taskTitle ?? t.taskId ?? "").trim() || `任务${idx + 1}`
1385
+ const status = String(t.statusText ?? t.status ?? t.taskBoardBucket ?? "未标记").trim()
1386
+ return `${idx + 1}. ${title}(${status})`
1387
+ })
1388
+ const lines = [
1389
+ `已获取「${projectName}」任务实况:共 ${tasks.length} 个执行任务,你认领 ${mine.length} 个,聚焦 ${focus.length} 个,等待依赖 ${waiting.length} 个,待认领 ${unclaimed.length} 个,已完成 ${done.length} 个。`,
1390
+ ]
1391
+ if (mineLines.length > 0) lines.push(`你当前认领的任务:\n${mineLines.join("\n")}`)
1392
+ return lines.join("\n")
1393
+ }
1394
+
1395
+ function buildDeterministicMemberTaskOverviewMessage(memberRaw: unknown, taskRaw: unknown): string {
1396
+ const memberData = asRecord(memberRaw)
1397
+ const taskData = asRecord(taskRaw)
1398
+ const projectName = pickProjectName(taskData) || pickProjectName(memberData)
1399
+ const members = Array.isArray(memberData.members) ? (memberData.members as Array<Record<string, unknown>>) : []
1400
+ const tasks = Array.isArray(taskData.executionTasks)
1401
+ ? (taskData.executionTasks as Array<Record<string, unknown>>)
1402
+ : Array.isArray(taskData.tasks)
1403
+ ? (taskData.tasks as Array<Record<string, unknown>>)
1404
+ : []
1405
+ const me = members.find((m) => m.isMe === true)
1406
+ const founderCount = members.filter((m) => String(m.role ?? "").trim().toLowerCase() === "founder").length
1407
+ const leaderCount = members.filter((m) => String(m.role ?? "").trim().toLowerCase() === "leader").length
1408
+ const memberCount = members.filter((m) => String(m.role ?? "").trim().toLowerCase() === "member").length
1409
+ const memberLines = members.map((m, idx) => {
1410
+ const displayName = String(m.displayName ?? m.nickname ?? m.lobsterId ?? "").trim() || `成员${idx + 1}`
1411
+ return `${idx + 1}. ${displayName}(${summarizeMemberRole(m.role)})`
1412
+ })
1413
+ const mine = tasks.filter((t) => t.claimedByMe === true || t.mine === true)
1414
+ const focus = tasks.filter((t) => String(t.taskBoardBucket ?? "").trim() === "focus")
1415
+ const waiting = tasks.filter((t) => String(t.taskBoardBucket ?? "").trim() === "waiting")
1416
+ const unclaimed = tasks.filter((t) => String(t.taskBoardBucket ?? "").trim() === "unclaimed")
1417
+ const done = tasks.filter((t) => String(t.taskBoardBucket ?? "").trim() === "done")
1418
+ const mineLines = mine.map((t, idx) => {
1419
+ const title = String(t.title ?? t.taskTitle ?? t.taskId ?? "").trim() || `任务${idx + 1}`
1420
+ const status = String(t.statusText ?? t.status ?? t.taskBoardBucket ?? "未标记").trim()
1421
+ return `${idx + 1}. ${title}(${status})`
1422
+ })
1423
+ const lines = [
1424
+ `已获取「${projectName}」成员与任务实况。`,
1425
+ `项目成员共 ${members.length} 人:创始人 ${founderCount} 人,负责人 ${leaderCount} 人,成员 ${memberCount} 人。`,
1426
+ memberLines.length > 0 ? `成员名单:\n${memberLines.join("\n")}` : "成员名单:暂无数据。",
1427
+ me ? `你当前在该项目中的角色:${summarizeMemberRole(me.role)}。` : "你当前在该项目中的角色:未识别。",
1428
+ `任务共 ${tasks.length} 个执行任务:你认领 ${mine.length} 个,聚焦 ${focus.length} 个,等待依赖 ${waiting.length} 个,待认领 ${unclaimed.length} 个,已完成 ${done.length} 个。`,
1429
+ mineLines.length > 0 ? `你当前认领的任务:\n${mineLines.join("\n")}` : "你当前认领的任务:0 个。",
1430
+ ]
1431
+ return lines.join("\n")
1432
+ }
1433
+
1434
+ function buildDeterministicMemberListMessage(memberRaw: unknown): string {
1435
+ const memberData = asRecord(memberRaw)
1436
+ const projectName = pickProjectName(memberData)
1437
+ const members = Array.isArray(memberData.members) ? (memberData.members as Array<Record<string, unknown>>) : []
1438
+ const me = members.find((m) => m.isMe === true)
1439
+ const founderCount = members.filter((m) => String(m.role ?? "").trim().toLowerCase() === "founder").length
1440
+ const leaderCount = members.filter((m) => String(m.role ?? "").trim().toLowerCase() === "leader").length
1441
+ const memberCount = members.filter((m) => String(m.role ?? "").trim().toLowerCase() === "member").length
1442
+ const memberLines = members.map((m, idx) => {
1443
+ const displayName = String(m.displayName ?? m.nickname ?? m.lobsterId ?? "").trim() || `成员${idx + 1}`
1444
+ return `${idx + 1}. ${displayName}(${summarizeMemberRole(m.role)})`
1445
+ })
1446
+ const lines = [
1447
+ `已获取「${projectName}」成员实况:共 ${members.length} 人,创始人 ${founderCount} 人,负责人 ${leaderCount} 人,成员 ${memberCount} 人。`,
1448
+ memberLines.length > 0 ? `成员名单:\n${memberLines.join("\n")}` : "成员名单:暂无数据。",
1449
+ me ? `你当前在该项目中的角色:${summarizeMemberRole(me.role)}。` : "你当前在该项目中的角色:未识别。",
1450
+ ]
1451
+ return lines.join("\n")
1452
+ }
1453
+
1454
+ function shortenSummaryText(raw: unknown, maxChars = 12): string {
1455
+ const text = String(raw ?? "").trim()
1456
+ if (!text) return ""
1457
+ return text.length > maxChars ? `${text.slice(0, maxChars)}...` : text
1458
+ }
1459
+
1460
+ function buildTaskListSummary(core: unknown, actionKey?: string): string | undefined {
1461
+ if (actionKey !== "task.list" && actionKey !== "task.list_all") return undefined
1462
+ const c = asRecord(core)
1463
+ const tasks = Array.isArray(c.tasks) ? (c.tasks as Array<Record<string, unknown>>) : []
1464
+ if (!tasks.length) {
1465
+ return actionKey === "task.list_all" ? "已汇总多项目任务,当前没有可展示的任务。" : `已获取「${pickProjectName(c)}」任务,当前没有任务。`
1466
+ }
1467
+ const mine = tasks.filter((t) => t.claimedByMe === true || t.mine === true)
1468
+ const focus = tasks.filter((t) => String(t.taskBoardBucket ?? t.bucket ?? "").trim() === "focus")
1469
+ const waiting = tasks.filter((t) => {
1470
+ const bucket = String(t.taskBoardBucket ?? t.bucket ?? "").trim()
1471
+ const status = String(t.status ?? "").trim()
1472
+ return bucket === "waiting" || status === "blocked" || status === "waiting"
1473
+ })
1474
+ const unclaimed = tasks.filter((t) => !String(t.assigneeLobsterId ?? "").trim() && t.claimedByMe !== true && t.mine !== true)
1475
+ const done = tasks.filter((t) => String(t.status ?? "").trim() === "done")
1476
+ const mineTitles = mine
1477
+ .map((t) => shortenSummaryText(t.title ?? t.taskTitle ?? t.taskId, 10))
1478
+ .filter(Boolean)
1479
+ .slice(0, 3)
1480
+ const prefix = actionKey === "task.list_all" ? "已汇总多项目任务" : `已获取「${pickProjectName(c)}」任务`
1481
+ let out = `${prefix}:共 ${tasks.length} 个,你认领 ${mine.length} 个,聚焦 ${focus.length} 个,待认领 ${unclaimed.length} 个`
1482
+ if (waiting.length > 0) out += `,等待依赖 ${waiting.length} 个`
1483
+ if (done.length > 0) out += `,已完成 ${done.length} 个`
1484
+ out += "。"
1485
+ if (mineTitles.length > 0) out += `你当前认领:${mineTitles.join("、")}。`
1486
+ return out
1487
+ }
1488
+
1233
1489
  function detectReplyActionKey(argv: string[], core: unknown): string | undefined {
1234
1490
  if (!argv.length) return undefined
1491
+ let actionKey: string | undefined
1235
1492
  if (argv[0] === "runtime-upgrade") return "runtime.upgrade"
1236
1493
  if (argv[0] === "dashboard-web") return "dashboard.web"
1237
1494
  if (argv[0] === "dashboard-all") return "todo.plan"
@@ -1261,18 +1518,20 @@ function detectReplyActionKey(argv: string[], core: unknown): string | undefined
1261
1518
  if (argv[0] === "task" && argv[1] === "update") return "task.update"
1262
1519
  if (argv[0] === "task" && argv[1] === "delete") return CAPABILITY_ACTION.TASK_DELETE
1263
1520
  if (argv[0] === "task" && argv[1] === "submit") return "task.complete"
1521
+ if (argv[0] === "task" && argv[1] === "list") actionKey = "task.list"
1522
+ if (argv[0] === "task" && argv[1] === "list-all") actionKey = "task.list_all"
1264
1523
  if (argv[0] === "member" && argv[1] === "remove") return "member.remove"
1265
1524
  if (argv[0] === "member" && argv[1] === "leave") return "member.leave"
1266
1525
  if (argv[0] === "doc" && argv[1] === "upload") return "doc.upload"
1267
1526
  if (argv[0] === "doc" && argv[1] === "delete") return CAPABILITY_ACTION.DOC_DELETE
1268
- if (argv[0] === "task" && (argv[1] === "list" || argv[1] === "list-all")) {
1527
+ if (actionKey === "task.list" || actionKey === "task.list_all") {
1269
1528
  const c = asRecord(core)
1270
1529
  const tasks = Array.isArray(c.tasks) ? c.tasks as Array<Record<string, unknown>> : []
1271
1530
  const now = Date.now()
1272
1531
  const overdue = tasks.find((t) => typeof t.dueAt === "number" && Number(t.dueAt) < now && String(t.status ?? "") !== "done")
1273
1532
  if (overdue) return "task.list.overdue"
1274
1533
  }
1275
- return undefined
1534
+ return actionKey
1276
1535
  }
1277
1536
 
1278
1537
  function renderReplyTemplate(actionKey: string | undefined, core: unknown, templates: ReplyTemplateMap): string | undefined {
@@ -1313,6 +1572,8 @@ function conciseMessageFromCore(core: unknown, actionKey?: string, templates: Re
1313
1572
  if ((actionKey === "invite.generate" || actionKey === "account.invite-join-template") && typeof c.inviteText === "string" && c.inviteText.trim()) {
1314
1573
  return c.inviteText.trim()
1315
1574
  }
1575
+ const taskSummary = buildTaskListSummary(c, actionKey)
1576
+ if (taskSummary) return taskSummary
1316
1577
  const todoPlan = c.currentTodoPlan as { items?: Array<Record<string, unknown>> } | undefined
1317
1578
  if (todoPlan && Array.isArray(todoPlan.items)) {
1318
1579
  const items = todoPlan.items
@@ -2575,6 +2836,64 @@ function fromAction(input: SkillInput): string[] {
2575
2836
  case "task.delete":
2576
2837
  if (!input.projectId || !input.taskId) throw new Error("missing_projectId_or_taskId")
2577
2838
  return ["task", "delete", "--identity", identity, "--project", input.projectId, "--task", input.taskId]
2839
+ case "task.link-project":
2840
+ case "task.link_project":
2841
+ if (!input.projectId || !input.taskId) throw new Error("missing_projectId_or_taskId")
2842
+ {
2843
+ const linkedProjectIds = String(input.linkedProjectIds ?? input.linkedProjectId ?? "").trim()
2844
+ if (!linkedProjectIds) throw new Error("missing_linkedProjectId")
2845
+ return [
2846
+ "task",
2847
+ "link-project",
2848
+ "--identity",
2849
+ identity,
2850
+ "--project",
2851
+ input.projectId,
2852
+ "--task",
2853
+ input.taskId,
2854
+ "--linkedProject",
2855
+ linkedProjectIds,
2856
+ ...(input.mode ? ["--mode", String(input.mode)] : []),
2857
+ ]
2858
+ }
2859
+ case "task.unlink-project":
2860
+ case "task.unlink_project":
2861
+ if (!input.projectId || !input.taskId) throw new Error("missing_projectId_or_taskId")
2862
+ return [
2863
+ "task",
2864
+ "unlink-project",
2865
+ "--identity",
2866
+ identity,
2867
+ "--project",
2868
+ input.projectId,
2869
+ "--task",
2870
+ input.taskId,
2871
+ ...(input.linkedProjectIds || input.linkedProjectId ? ["--linkedProject", String(input.linkedProjectIds ?? input.linkedProjectId)] : []),
2872
+ ]
2873
+ case "task.list-linked-project":
2874
+ case "task.list_linked_project":
2875
+ if (!input.projectId || !input.taskId) throw new Error("missing_projectId_or_taskId")
2876
+ return ["task", "list-linked-project", "--identity", identity, "--project", input.projectId, "--task", input.taskId]
2877
+ case "project.link-task":
2878
+ case "project.link_task":
2879
+ {
2880
+ const linkedProjectId = String(input.linkedProjectId ?? input.projectId ?? "").trim()
2881
+ const targetProjectId = String(input.targetProjectId ?? "").trim()
2882
+ if (!linkedProjectId || !targetProjectId || !input.taskId) throw new Error("missing_linkedProjectId_or_targetProjectId_or_taskId")
2883
+ return [
2884
+ "project",
2885
+ "link-task",
2886
+ "--identity",
2887
+ identity,
2888
+ "--project",
2889
+ linkedProjectId,
2890
+ "--targetProject",
2891
+ targetProjectId,
2892
+ "--task",
2893
+ input.taskId,
2894
+ ...(input.mode ? ["--mode", String(input.mode)] : []),
2895
+ ]
2896
+ }
2578
2897
  case "task.suggest":
2579
2898
  {
2580
2899
  const target = String(input.to ?? input.targetLobsterId ?? input.memberId ?? "").trim()
@@ -3356,6 +3675,7 @@ async function main(): Promise<void> {
3356
3675
  process.stdout.write(`${JSON.stringify(out, null, 2)}\n`)
3357
3676
  return
3358
3677
  }
3678
+ await ensureSongXingBindingIfNeeded(identityPath, input, text, rules, contextState)
3359
3679
  const runCliCaptured = async (argv: string[]): Promise<unknown> => {
3360
3680
  let captured = ""
3361
3681
  const originalWrite = process.stdout.write.bind(process.stdout)
@@ -3378,6 +3698,23 @@ async function main(): Promise<void> {
3378
3698
  return captured.trim()
3379
3699
  }
3380
3700
  }
3701
+ if (!input.projectId && !input.action) {
3702
+ try {
3703
+ const projectList = (await runCliCaptured(["project", "list", "--identity", identityPath])) as any
3704
+ const projects = Array.isArray(projectList?.projects) ? projectList.projects : []
3705
+ const queryText = normalizeProjectQueryText(text)
3706
+ const matched =
3707
+ projects.find((p: any) => normalizeProjectQueryText(String(p?.projectId ?? "")) && queryText.includes(normalizeProjectQueryText(String(p?.projectId ?? "")))) ??
3708
+ projects.find((p: any) => {
3709
+ const nameKey = normalizeProjectQueryText(String(p?.name ?? ""))
3710
+ return nameKey && queryText.includes(nameKey)
3711
+ })
3712
+ if (matched?.projectId) {
3713
+ input.projectId = String(matched.projectId)
3714
+ }
3715
+ } catch {
3716
+ }
3717
+ }
3381
3718
 
3382
3719
  const pendingFlow = parsePendingFlow(contextState.unfinishedIntent)
3383
3720
  if (pendingFlow?.kind === "p0_project_delete_confirm") {
@@ -3565,7 +3902,7 @@ async function main(): Promise<void> {
3565
3902
  const projects = Array.isArray(projectList?.projects) ? projectList.projects : []
3566
3903
  const answer = text.trim()
3567
3904
  if (!answer) {
3568
- const listText = projects.slice(0, 8).map((p: any, idx: number) => `${idx + 1}. ${p.name}(${p.projectId})`).join("\n")
3905
+ const listText = projects.map((p: any, idx: number) => `${idx + 1}. ${p.name}(${p.projectId})`).join("\n")
3569
3906
  const out: SkillSuccess = {
3570
3907
  ok: true,
3571
3908
  data: {
@@ -3585,7 +3922,7 @@ async function main(): Promise<void> {
3585
3922
  projects.find((p: any) => String(p.name) === answer) ??
3586
3923
  projects.find((p: any) => String(p.name ?? "").includes(answer))
3587
3924
  if (!matched) {
3588
- const listText = projects.slice(0, 8).map((p: any, idx: number) => `${idx + 1}. ${p.name}(${p.projectId})`).join("\n")
3925
+ const listText = projects.map((p: any, idx: number) => `${idx + 1}. ${p.name}(${p.projectId})`).join("\n")
3589
3926
  const out: SkillSuccess = {
3590
3927
  ok: true,
3591
3928
  data: {
@@ -3674,7 +4011,7 @@ async function main(): Promise<void> {
3674
4011
  const projectList = (await runCliCaptured(["project", "list", "--identity", identityPath])) as any
3675
4012
  const projects = Array.isArray(projectList?.projects) ? projectList.projects : []
3676
4013
  const answer = text.trim()
3677
- const listText = projects.slice(0, 8).map((p: any, idx: number) => `${idx + 1}. ${p.name}(${p.projectId})`).join("\n")
4014
+ const listText = projects.map((p: any, idx: number) => `${idx + 1}. ${p.name}(${p.projectId})`).join("\n")
3678
4015
  if (!answer) {
3679
4016
  const out: SkillSuccess = {
3680
4017
  ok: true,
@@ -3744,7 +4081,7 @@ async function main(): Promise<void> {
3744
4081
  : Array.isArray(taskList?.data?.tasks)
3745
4082
  ? taskList.data.tasks
3746
4083
  : []
3747
- const listText = tasks.slice(0, 12).map((t: any, idx: number) => `${idx + 1}. ${t.title || t.taskId}(${t.taskId})`).join("\n")
4084
+ const listText = tasks.map((t: any, idx: number) => `${idx + 1}. ${t.title || t.taskId}(${t.taskId})`).join("\n")
3748
4085
  if (!answer) {
3749
4086
  const out: SkillSuccess = {
3750
4087
  ok: true,
@@ -4056,7 +4393,7 @@ async function main(): Promise<void> {
4056
4393
  const projectList = (await runCliCaptured(["project", "list", "--identity", identityPath])) as any
4057
4394
  const projects = Array.isArray(projectList?.projects) ? projectList.projects : []
4058
4395
  const answer = text.trim()
4059
- const listText = projects.slice(0, 8).map((p: any, idx: number) => `${idx + 1}. ${p.name}(${p.projectId})`).join("\n")
4396
+ const listText = projects.map((p: any, idx: number) => `${idx + 1}. ${p.name}(${p.projectId})`).join("\n")
4060
4397
  if (!answer) {
4061
4398
  const out: SkillSuccess = {
4062
4399
  ok: true,
@@ -4125,7 +4462,7 @@ async function main(): Promise<void> {
4125
4462
  : Array.isArray(docList?.data?.docs)
4126
4463
  ? docList.data.docs
4127
4464
  : []
4128
- const listText = docs.slice(0, 12).map((d: any, idx: number) => `${idx + 1}. ${d.docName || d.docId}(${d.docId})`).join("\n")
4465
+ const listText = docs.map((d: any, idx: number) => `${idx + 1}. ${d.docName || d.docId}(${d.docId})`).join("\n")
4129
4466
  if (!answer) {
4130
4467
  const out: SkillSuccess = {
4131
4468
  ok: true,
@@ -4227,7 +4564,7 @@ async function main(): Promise<void> {
4227
4564
  }
4228
4565
  const projectList = (await runCliCaptured(["project", "list", "--identity", identityPath])) as any
4229
4566
  const projects = Array.isArray(projectList?.projects) ? projectList.projects : []
4230
- const listText = projects.slice(0, 8).map((p: any, idx: number) => `${idx + 1}. ${p.name}(${p.projectId})`).join("\n")
4567
+ const listText = projects.map((p: any, idx: number) => `${idx + 1}. ${p.name}(${p.projectId})`).join("\n")
4231
4568
  const nextState: MicroContextState = {
4232
4569
  ...contextState,
4233
4570
  lastMicroTaskTime: Date.now(),
@@ -4318,6 +4655,89 @@ async function main(): Promise<void> {
4318
4655
  return
4319
4656
  }
4320
4657
  }
4658
+ if (!input.action && input.projectId && isProjectMemberQueryText(text) && isProjectTaskQueryText(text)) {
4659
+ const memberData = await runCliCaptured(["member", "list", "--identity", identityPath, "--project", input.projectId])
4660
+ const taskData = await runCliCaptured(["task", "list", "--identity", identityPath, "--project", input.projectId, "--includeContainers", "true", "--includeArchived", "true"])
4661
+ const mergedCore = {
4662
+ ...(asRecord(taskData) as Record<string, unknown>),
4663
+ projectId: input.projectId,
4664
+ projectName: pickProjectName(taskData) || pickProjectName(memberData) || input.projectId,
4665
+ members: Array.isArray((asRecord(memberData) as any).members) ? (asRecord(memberData) as any).members : [],
4666
+ }
4667
+ const out: SkillSuccess = {
4668
+ ok: true,
4669
+ data: {
4670
+ result: ensureDialogueResultContract({
4671
+ ...(mergedCore as any),
4672
+ message: buildDeterministicMemberTaskOverviewMessage(memberData, taskData),
4673
+ card: {
4674
+ result: "成员与任务实况已同步",
4675
+ status: "已按真实数据生成",
4676
+ nextAction: "下一步:可继续说“只看我认领的任务”或“只看待认领任务”",
4677
+ },
4678
+ protocol: { channel: "weiyuan", version: 1, prefix: MICRO_PREFIX, actionKey: "project.member_task.overview" },
4679
+ }),
4680
+ contextTip: "项目成员与任务",
4681
+ detailMode: detailed ? "full" : "core",
4682
+ },
4683
+ }
4684
+ process.stdout.write(`${JSON.stringify(out, null, 2)}\n`)
4685
+ return
4686
+ }
4687
+ if (!input.action && input.projectId && isProjectTaskQueryText(text) && !isProjectMemberQueryText(text)) {
4688
+ const taskData = await runCliCaptured(["task", "list", "--identity", identityPath, "--project", input.projectId, "--includeContainers", "true", "--includeArchived", "true"])
4689
+ const taskCore = {
4690
+ ...(asRecord(taskData) as Record<string, unknown>),
4691
+ projectId: input.projectId,
4692
+ projectName: pickProjectName(taskData) || input.projectId,
4693
+ }
4694
+ const out: SkillSuccess = {
4695
+ ok: true,
4696
+ data: {
4697
+ result: ensureDialogueResultContract({
4698
+ ...(taskCore as any),
4699
+ message: buildDeterministicTaskListMessage(taskData) ?? `已获取「${input.projectId}」任务实况。`,
4700
+ card: {
4701
+ result: "任务实况已同步",
4702
+ status: "已按真实数据生成",
4703
+ nextAction: "下一步:可继续说“只看我认领的任务”或“看待认领任务”",
4704
+ },
4705
+ protocol: { channel: "weiyuan", version: 1, prefix: MICRO_PREFIX, actionKey: "project.task.overview" },
4706
+ }),
4707
+ contextTip: "项目任务",
4708
+ detailMode: detailed ? "full" : "core",
4709
+ },
4710
+ }
4711
+ process.stdout.write(`${JSON.stringify(out, null, 2)}\n`)
4712
+ return
4713
+ }
4714
+ if (!input.action && input.projectId && isProjectMemberQueryText(text) && !isProjectTaskQueryText(text)) {
4715
+ const memberData = await runCliCaptured(["member", "list", "--identity", identityPath, "--project", input.projectId])
4716
+ const memberCore = {
4717
+ ...(asRecord(memberData) as Record<string, unknown>),
4718
+ projectId: input.projectId,
4719
+ projectName: pickProjectName(memberData) || input.projectId,
4720
+ }
4721
+ const out: SkillSuccess = {
4722
+ ok: true,
4723
+ data: {
4724
+ result: ensureDialogueResultContract({
4725
+ ...(memberCore as any),
4726
+ message: buildDeterministicMemberListMessage(memberData),
4727
+ card: {
4728
+ result: "成员实况已同步",
4729
+ status: "已按真实数据生成",
4730
+ nextAction: "下一步:可继续说“看成员详情”或“看人员和任务情况”",
4731
+ },
4732
+ protocol: { channel: "weiyuan", version: 1, prefix: MICRO_PREFIX, actionKey: "project.member.overview" },
4733
+ }),
4734
+ contextTip: "项目成员",
4735
+ detailMode: detailed ? "full" : "core",
4736
+ },
4737
+ }
4738
+ process.stdout.write(`${JSON.stringify(out, null, 2)}\n`)
4739
+ return
4740
+ }
4321
4741
  if (!input.action && /(确认)?(删除|移除).*(项目)/.test(compactText)) {
4322
4742
  if (!input.projectId) {
4323
4743
  const out: SkillSuccess = {
@@ -4615,6 +5035,27 @@ function mapError(err: unknown, detailed: boolean): SkillError {
4615
5035
  if (rawError.includes("init_cancelled_existing_identity")) {
4616
5036
  return { ok: false, errorCode: "CLI_ERROR", message: "已取消初始化。当前身份保持不变。", ...(detailed ? { rawError } : {}) }
4617
5037
  }
5038
+ if (rawError.includes("songxing_manifest_required")) {
5039
+ return { ok: false, errorCode: "CLI_ERROR", message: "当前 SongXing 专供实例缺少 manifest 文件,无法自动完成绑定。", ...(detailed ? { rawError } : {}) }
5040
+ }
5041
+ if (rawError.includes("songxing_workspace_required")) {
5042
+ return { ok: false, errorCode: "CLI_ERROR", message: "当前工作空间标识缺失,暂时无法执行 SongXing 自动绑定。", ...(detailed ? { rawError } : {}) }
5043
+ }
5044
+ if (rawError.includes("songxing_account_not_found")) {
5045
+ return { ok: false, errorCode: "CLI_ERROR", message: "SongXing 交付账号不存在,请重新导出该实例的交付包。", ...(detailed ? { rawError } : {}) }
5046
+ }
5047
+ if (rawError.includes("songxing_account_deleted")) {
5048
+ return { ok: false, errorCode: "CLI_ERROR", message: "这个 SongXing 账号已被后台停用,请更换新的交付账号。", ...(detailed ? { rawError } : {}) }
5049
+ }
5050
+ if (rawError.includes("songxing_delivery_code_invalid")) {
5051
+ return { ok: false, errorCode: "CLI_ERROR", message: "SongXing 校验码无效,当前实例不能自动绑定。", ...(detailed ? { rawError } : {}) }
5052
+ }
5053
+ if (rawError.includes("songxing_manifest_workspace_mismatch")) {
5054
+ return { ok: false, errorCode: "CLI_ERROR", message: "当前 SongXing 清单已绑定到其他工作空间,系统已拒绝串号。", ...(detailed ? { rawError } : {}) }
5055
+ }
5056
+ if (rawError.includes("songxing_secret_missing") || rawError.includes("songxing_identity_payload_missing")) {
5057
+ return { ok: false, errorCode: "CLI_ERROR", message: "SongXing 身份回填数据缺失,请重新导出并注入交付包。", ...(detailed ? { rawError } : {}) }
5058
+ }
4618
5059
  if (rawError.includes("init_requires_confirm_existing_identity_use_yes")) {
4619
5060
  return { ok: false, errorCode: "CLI_ERROR", message: "检测到已有身份。请先完成人工确认后再初始化。", ...(detailed ? { rawError } : {}) }
4620
5061
  }