diff --git a/.env.dev.example b/.env.dev.example new file mode 100644 index 000000000..a3be2680f --- /dev/null +++ b/.env.dev.example @@ -0,0 +1,12 @@ +# Local secrets for the repository's Lark role bots. +# Copy this file to .env.dev and fill values locally. Never commit .env.dev. +ONEWORKS_LARK_PRODUCT_TEAM_APP_SECRET= +ONEWORKS_LARK_WAN_KE_ANALYTICS_APP_SECRET= +ONEWORKS_LARK_WAN_KE_DESIGN_APP_SECRET= +ONEWORKS_LARK_WAN_KE_ENGINEERING_APP_SECRET= +ONEWORKS_LARK_WAN_KE_FRONTEND_APP_SECRET= +ONEWORKS_LARK_WAN_KE_OPERATIONS_APP_SECRET= +ONEWORKS_LARK_WAN_KE_OPS_APP_SECRET= +ONEWORKS_LARK_WAN_KE_PRODUCT_APP_SECRET= +ONEWORKS_LARK_WAN_KE_PROJECT_APP_SECRET= +ONEWORKS_LARK_WAN_KE_TEST_APP_SECRET= diff --git a/.gitignore b/.gitignore index 7e4bf1fe0..4964b7415 100644 --- a/.gitignore +++ b/.gitignore @@ -100,6 +100,7 @@ web_modules/ # dotenv environment variable files .env +.env.dev .env.development.local .env.test.local .env.production.local diff --git a/.oo.config.json b/.oo.config.json index b8586fd4b..e2867bb44 100644 --- a/.oo.config.json +++ b/.oo.config.json @@ -1,4 +1,6 @@ { + "defaultAdapter": "codex", + "defaultModel": "gpt-5.6-luna", "adapters": { "codex": {}, "claude-code": { @@ -50,6 +52,10 @@ "id": "./packages/plugins/warm-cowork-theme", "scope": "warm-cowork-theme", "watch": true + }, + { + "id": "@oneworks/plugin-channel-oneworks", + "watch": true } ], "modelServices": { @@ -320,5 +326,218 @@ "appearance": { "primaryColor": "#8B9493", "iconBackground": "solid" + }, + "channels": { + "lark:product-team": { + "type": "lark", + "title": "OWO 万可演示服务", + "description": "OWO 在万可闲聊中的独立演示服务连接。", + "appId": "cli_aaba90fcc4389cb3", + "appSecret": "${ONEWORKS_LARK_PRODUCT_TEAM_APP_SECRET}", + "domain": "Feishu", + "language": "zh", + "groupMessageDebounceMs": 800, + "systemPrompt": "你是 OWO,万可闲聊中的独立演示服务。负责展示 OneWorks 的对话、记忆与团队协调能力,并在需要时明确邀请产品或运营机器人参与。\n\n一介[字节] 是外部体验方,回复保持轻量、具体、可推进;不要冒充其它角色,也不要把其它群的私有上下文带入当前回复。", + "access": { + "allowPrivateChat": false, + "allowGroupChat": true, + "allowedGroups": [ + "oc_e533f5906d734ec0061633176b31aedf" + ], + "admins": [ + "ou_4109cc08559eaabc187bba1bb88891c8" + ] + } + }, + "lark:wan-ke-product": { + "type": "lark", + "title": "OwO【产品】", + "description": "万可产品角色机器人连接。", + "appId": "cli_aaa567886c389cca", + "appSecret": "${ONEWORKS_LARK_WAN_KE_PRODUCT_APP_SECRET}", + "domain": "Feishu", + "language": "zh", + "groupMessageDebounceMs": 800, + "access": { + "allowPrivateChat": false, + "allowGroupChat": true, + "allowedGroups": [ + "oc_e533f5906d734ec0061633176b31aedf", + "oc_20b024001d5a2ab237b8c82f9595a099", + "oc_aa463b23f97a13e63b53e5d99cb791d9" + ], + "admins": [ + "ou_f9c16d7343f34c839443b980a50c7546" + ] + } + }, + "lark:wan-ke-design": { + "type": "lark", + "title": "Ovo【设计】", + "description": "万可设计角色机器人连接。", + "appId": "cli_aaa5600758789cc9", + "appSecret": "${ONEWORKS_LARK_WAN_KE_DESIGN_APP_SECRET}", + "domain": "Feishu", + "language": "zh", + "groupMessageDebounceMs": 800, + "access": { + "allowPrivateChat": false, + "allowGroupChat": true, + "allowedGroups": [ + "oc_20b024001d5a2ab237b8c82f9595a099" + ], + "admins": [ + "ou_9d407f5ea1807328f6f2e9f893ffc6b6" + ] + } + }, + "lark:wan-ke-frontend": { + "type": "lark", + "title": "Owo【前端】", + "description": "万可前端角色机器人连接。", + "appId": "cli_aaa5abdf3038dcd2", + "appSecret": "${ONEWORKS_LARK_WAN_KE_FRONTEND_APP_SECRET}", + "domain": "Feishu", + "language": "zh", + "groupMessageDebounceMs": 800, + "access": { + "allowPrivateChat": false, + "allowGroupChat": true, + "allowedGroups": [ + "oc_b857784ed7ae69638288d78a61ec7909", + "oc_4f78c1fbad9da5205b7389000e33f30a", + "oc_711d58b78dfc1988e0616d956b870d39" + ], + "admins": [ + "ou_8a5683b6c6436edb19c7361278360b27" + ] + } + }, + "lark:wan-ke-engineering": { + "type": "lark", + "title": "qxq【研发】", + "description": "万科研发角色机器人连接。", + "appId": "cli_aaa567c16df91cbb", + "appSecret": "${ONEWORKS_LARK_WAN_KE_ENGINEERING_APP_SECRET}", + "domain": "Feishu", + "language": "zh", + "groupMessageDebounceMs": 800, + "access": { + "allowPrivateChat": false, + "allowGroupChat": true, + "allowedGroups": [ + "oc_b857784ed7ae69638288d78a61ec7909", + "oc_4f78c1fbad9da5205b7389000e33f30a", + "oc_711d58b78dfc1988e0616d956b870d39" + ], + "admins": [ + "ou_85c8b5acc4eeb6bd7633948eca40ae6e" + ] + } + }, + "lark:wan-ke-test": { + "type": "lark", + "title": "qwq【测试】", + "description": "万可测试角色机器人连接。", + "appId": "cli_aaa567f1faf95cdc", + "appSecret": "${ONEWORKS_LARK_WAN_KE_TEST_APP_SECRET}", + "domain": "Feishu", + "language": "zh", + "groupMessageDebounceMs": 800, + "access": { + "allowPrivateChat": false, + "allowGroupChat": true, + "allowedGroups": [ + "oc_b857784ed7ae69638288d78a61ec7909", + "oc_711d58b78dfc1988e0616d956b870d39" + ], + "admins": [ + "ou_7714a798ac7aae953115003528147512" + ] + } + }, + "lark:wan-ke-ops": { + "type": "lark", + "title": "0W0【运维】", + "description": "万可运维角色机器人连接。", + "appId": "cli_aaa567e1b6391cc2", + "appSecret": "${ONEWORKS_LARK_WAN_KE_OPS_APP_SECRET}", + "domain": "Feishu", + "language": "zh", + "groupMessageDebounceMs": 800, + "access": { + "allowPrivateChat": false, + "allowGroupChat": true, + "allowedGroups": [ + "oc_4f78c1fbad9da5205b7389000e33f30a", + "oc_711d58b78dfc1988e0616d956b870d39" + ], + "admins": [ + "ou_de3770ae95b94f4c6638b7ab818a1216" + ] + } + }, + "lark:wan-ke-operations": { + "type": "lark", + "title": "UvU【运营】", + "description": "万可运营角色机器人连接。", + "appId": "cli_aaa5601603385ce7", + "appSecret": "${ONEWORKS_LARK_WAN_KE_OPERATIONS_APP_SECRET}", + "domain": "Feishu", + "language": "zh", + "groupMessageDebounceMs": 800, + "access": { + "allowPrivateChat": false, + "allowGroupChat": true, + "allowedGroups": [ + "oc_e533f5906d734ec0061633176b31aedf", + "oc_aa463b23f97a13e63b53e5d99cb791d9" + ], + "admins": [ + "ou_8b9a9e4d5c1a4104adf19ad013f4fe52" + ] + } + }, + "lark:wan-ke-analytics": { + "type": "lark", + "title": "q0q【数分】", + "description": "万可数分角色机器人连接。", + "appId": "cli_aaa5603707b95ccc", + "appSecret": "${ONEWORKS_LARK_WAN_KE_ANALYTICS_APP_SECRET}", + "domain": "Feishu", + "language": "zh", + "groupMessageDebounceMs": 800, + "access": { + "allowPrivateChat": false, + "allowGroupChat": true, + "allowedGroups": [ + "oc_4f78c1fbad9da5205b7389000e33f30a", + "oc_aa463b23f97a13e63b53e5d99cb791d9" + ], + "admins": [ + "ou_1fea8206ec214ce3c0a915d211626c14" + ] + } + }, + "lark:wan-ke-project": { + "type": "lark", + "title": "n_n【项目】", + "description": "万可项目管理角色机器人连接。", + "appId": "cli_aabaa692a238dcd2", + "appSecret": "${ONEWORKS_LARK_WAN_KE_PROJECT_APP_SECRET}", + "domain": "Feishu", + "language": "zh", + "groupMessageDebounceMs": 800, + "access": { + "allowPrivateChat": false, + "allowGroupChat": true, + "allowedGroups": [ + "oc_711d58b78dfc1988e0616d956b870d39" + ], + "admins": [ + "ou_6d86fcc87bfe8b1df9ed68ce7edf2c9a" + ] + } + } } } diff --git a/.oo/channels/wan-ke-brainstorm-design/channel.json b/.oo/channels/wan-ke-brainstorm-design/channel.json new file mode 100644 index 000000000..40876d722 --- /dev/null +++ b/.oo/channels/wan-ke-brainstorm-design/channel.json @@ -0,0 +1,26 @@ +{ + "name": "万可脑爆 · Ovo【设计】", + "description": "产品与设计的需求脑暴和决策群。", + "channel": "lark:wan-ke-design", + "entity": "wan-ke-design", + "external": { + "type": "chat", + "chatId": "oc_20b024001d5a2ab237b8c82f9595a099" + }, + "ingress": { + "ambientRouting": false, + "createOnCommand": false, + "createOnMention": true, + "mentionPatterns": [ + "@Ovo【设计】", + "@Ovo" + ] + }, + "authorization": { + "deliveryThrottleMs": 1200000, + "resume": { + "mode": "immediate", + "delayMs": 0 + } + } +} diff --git a/.oo/channels/wan-ke-brainstorm-product/channel.json b/.oo/channels/wan-ke-brainstorm-product/channel.json new file mode 100644 index 000000000..f14328879 --- /dev/null +++ b/.oo/channels/wan-ke-brainstorm-product/channel.json @@ -0,0 +1,26 @@ +{ + "name": "万可脑爆 · OwO【产品】", + "description": "产品与设计的需求脑暴和决策群。", + "channel": "lark:wan-ke-product", + "entity": "wan-ke-product", + "external": { + "type": "chat", + "chatId": "oc_20b024001d5a2ab237b8c82f9595a099" + }, + "ingress": { + "ambientRouting": false, + "createOnCommand": false, + "createOnMention": true, + "mentionPatterns": [ + "@OwO【产品】", + "@OwO" + ] + }, + "authorization": { + "deliveryThrottleMs": 1200000, + "resume": { + "mode": "immediate", + "delayMs": 0 + } + } +} diff --git a/.oo/channels/wan-ke-chat-demo/channel.json b/.oo/channels/wan-ke-chat-demo/channel.json new file mode 100644 index 000000000..7e04eeaf6 --- /dev/null +++ b/.oo/channels/wan-ke-chat-demo/channel.json @@ -0,0 +1,23 @@ +{ + "name": "万可闲聊 · OWO", + "description": "产品、运营与 OWO 的轻量演示群。", + "channel": "lark:product-team", + "entity": "owo-demo", + "external": { + "type": "chat", + "chatId": "oc_e533f5906d734ec0061633176b31aedf" + }, + "ingress": { + "ambientRouting": false, + "createOnCommand": false, + "createOnMention": true, + "mentionPatterns": ["@OWO"] + }, + "authorization": { + "deliveryThrottleMs": 1200000, + "resume": { + "mode": "immediate", + "delayMs": 0 + } + } +} diff --git a/.oo/channels/wan-ke-chat-operations/channel.json b/.oo/channels/wan-ke-chat-operations/channel.json new file mode 100644 index 000000000..a8de4fbdf --- /dev/null +++ b/.oo/channels/wan-ke-chat-operations/channel.json @@ -0,0 +1,26 @@ +{ + "name": "万可闲聊 · UvU【运营】", + "description": "产品、运营与 OWO 的轻量演示群。", + "channel": "lark:wan-ke-operations", + "entity": "wan-ke-operations", + "external": { + "type": "chat", + "chatId": "oc_e533f5906d734ec0061633176b31aedf" + }, + "ingress": { + "ambientRouting": false, + "createOnCommand": false, + "createOnMention": true, + "mentionPatterns": [ + "@UvU【运营】", + "@UvU" + ] + }, + "authorization": { + "deliveryThrottleMs": 1200000, + "resume": { + "mode": "immediate", + "delayMs": 0 + } + } +} diff --git a/.oo/channels/wan-ke-chat-product/channel.json b/.oo/channels/wan-ke-chat-product/channel.json new file mode 100644 index 000000000..51a3dcb36 --- /dev/null +++ b/.oo/channels/wan-ke-chat-product/channel.json @@ -0,0 +1,26 @@ +{ + "name": "万可闲聊 · OwO【产品】", + "description": "产品、运营与 OWO 的轻量演示群。", + "channel": "lark:wan-ke-product", + "entity": "wan-ke-product", + "external": { + "type": "chat", + "chatId": "oc_e533f5906d734ec0061633176b31aedf" + }, + "ingress": { + "ambientRouting": false, + "createOnCommand": false, + "createOnMention": true, + "mentionPatterns": [ + "@OwO【产品】", + "@OwO" + ] + }, + "authorization": { + "deliveryThrottleMs": 1200000, + "resume": { + "mode": "immediate", + "delayMs": 0 + } + } +} diff --git a/.oo/channels/wan-ke-growth-analytics/channel.json b/.oo/channels/wan-ke-growth-analytics/channel.json new file mode 100644 index 000000000..721434945 --- /dev/null +++ b/.oo/channels/wan-ke-growth-analytics/channel.json @@ -0,0 +1,26 @@ +{ + "name": "万可增长 · q0q【数分】", + "description": "运营、数分与产品的增长实验群。", + "channel": "lark:wan-ke-analytics", + "entity": "wan-ke-analytics", + "external": { + "type": "chat", + "chatId": "oc_aa463b23f97a13e63b53e5d99cb791d9" + }, + "ingress": { + "ambientRouting": false, + "createOnCommand": false, + "createOnMention": true, + "mentionPatterns": [ + "@q0q【数分】", + "@q0q" + ] + }, + "authorization": { + "deliveryThrottleMs": 1200000, + "resume": { + "mode": "immediate", + "delayMs": 0 + } + } +} diff --git a/.oo/channels/wan-ke-growth-operations/channel.json b/.oo/channels/wan-ke-growth-operations/channel.json new file mode 100644 index 000000000..12013615e --- /dev/null +++ b/.oo/channels/wan-ke-growth-operations/channel.json @@ -0,0 +1,26 @@ +{ + "name": "万可增长 · UvU【运营】", + "description": "运营、数分与产品的增长实验群。", + "channel": "lark:wan-ke-operations", + "entity": "wan-ke-operations", + "external": { + "type": "chat", + "chatId": "oc_aa463b23f97a13e63b53e5d99cb791d9" + }, + "ingress": { + "ambientRouting": false, + "createOnCommand": false, + "createOnMention": true, + "mentionPatterns": [ + "@UvU【运营】", + "@UvU" + ] + }, + "authorization": { + "deliveryThrottleMs": 1200000, + "resume": { + "mode": "immediate", + "delayMs": 0 + } + } +} diff --git a/.oo/channels/wan-ke-growth-product/channel.json b/.oo/channels/wan-ke-growth-product/channel.json new file mode 100644 index 000000000..5dc9a32a7 --- /dev/null +++ b/.oo/channels/wan-ke-growth-product/channel.json @@ -0,0 +1,26 @@ +{ + "name": "万可增长 · OwO【产品】", + "description": "运营、数分与产品的增长实验群。", + "channel": "lark:wan-ke-product", + "entity": "wan-ke-product", + "external": { + "type": "chat", + "chatId": "oc_aa463b23f97a13e63b53e5d99cb791d9" + }, + "ingress": { + "ambientRouting": false, + "createOnCommand": false, + "createOnMention": true, + "mentionPatterns": [ + "@OwO【产品】", + "@OwO" + ] + }, + "authorization": { + "deliveryThrottleMs": 1200000, + "resume": { + "mode": "immediate", + "delayMs": 0 + } + } +} diff --git a/.oo/channels/wan-ke-monitor-analytics/channel.json b/.oo/channels/wan-ke-monitor-analytics/channel.json new file mode 100644 index 000000000..4442aaddd --- /dev/null +++ b/.oo/channels/wan-ke-monitor-analytics/channel.json @@ -0,0 +1,26 @@ +{ + "name": "万可监控 · q0q【数分】", + "description": "运维、研发、前端与数分的监控和应急群。", + "channel": "lark:wan-ke-analytics", + "entity": "wan-ke-analytics", + "external": { + "type": "chat", + "chatId": "oc_4f78c1fbad9da5205b7389000e33f30a" + }, + "ingress": { + "ambientRouting": false, + "createOnCommand": false, + "createOnMention": true, + "mentionPatterns": [ + "@q0q【数分】", + "@q0q" + ] + }, + "authorization": { + "deliveryThrottleMs": 1200000, + "resume": { + "mode": "immediate", + "delayMs": 0 + } + } +} diff --git a/.oo/channels/wan-ke-monitor-engineering/channel.json b/.oo/channels/wan-ke-monitor-engineering/channel.json new file mode 100644 index 000000000..4fbacee84 --- /dev/null +++ b/.oo/channels/wan-ke-monitor-engineering/channel.json @@ -0,0 +1,26 @@ +{ + "name": "万可监控 · qxq【研发】", + "description": "运维、研发、前端与数分的监控和应急群。", + "channel": "lark:wan-ke-engineering", + "entity": "wan-ke-engineering", + "external": { + "type": "chat", + "chatId": "oc_4f78c1fbad9da5205b7389000e33f30a" + }, + "ingress": { + "ambientRouting": false, + "createOnCommand": false, + "createOnMention": true, + "mentionPatterns": [ + "@qxq【研发】", + "@qxq" + ] + }, + "authorization": { + "deliveryThrottleMs": 1200000, + "resume": { + "mode": "immediate", + "delayMs": 0 + } + } +} diff --git a/.oo/channels/wan-ke-monitor-frontend/channel.json b/.oo/channels/wan-ke-monitor-frontend/channel.json new file mode 100644 index 000000000..6f068afd9 --- /dev/null +++ b/.oo/channels/wan-ke-monitor-frontend/channel.json @@ -0,0 +1,26 @@ +{ + "name": "万可监控 · Owo【前端】", + "description": "运维、研发、前端与数分的监控和应急群。", + "channel": "lark:wan-ke-frontend", + "entity": "wan-ke-frontend", + "external": { + "type": "chat", + "chatId": "oc_4f78c1fbad9da5205b7389000e33f30a" + }, + "ingress": { + "ambientRouting": false, + "createOnCommand": false, + "createOnMention": true, + "mentionPatterns": [ + "@Owo【前端】", + "@Owo" + ] + }, + "authorization": { + "deliveryThrottleMs": 1200000, + "resume": { + "mode": "immediate", + "delayMs": 0 + } + } +} diff --git a/.oo/channels/wan-ke-monitor-ops/channel.json b/.oo/channels/wan-ke-monitor-ops/channel.json new file mode 100644 index 000000000..58ec141b7 --- /dev/null +++ b/.oo/channels/wan-ke-monitor-ops/channel.json @@ -0,0 +1,26 @@ +{ + "name": "万可监控 · 0W0【运维】", + "description": "运维、研发、前端与数分的监控和应急群。", + "channel": "lark:wan-ke-ops", + "entity": "wan-ke-ops", + "external": { + "type": "chat", + "chatId": "oc_4f78c1fbad9da5205b7389000e33f30a" + }, + "ingress": { + "ambientRouting": false, + "createOnCommand": false, + "createOnMention": true, + "mentionPatterns": [ + "@0W0【运维】", + "@0W0" + ] + }, + "authorization": { + "deliveryThrottleMs": 1200000, + "resume": { + "mode": "immediate", + "delayMs": 0 + } + } +} diff --git a/.oo/channels/wan-ke-release-engineering/channel.json b/.oo/channels/wan-ke-release-engineering/channel.json new file mode 100644 index 000000000..550d0b949 --- /dev/null +++ b/.oo/channels/wan-ke-release-engineering/channel.json @@ -0,0 +1,26 @@ +{ + "name": "万可发布 · qxq【研发】", + "description": "项目、前端、研发、测试与运维的发布协同群。", + "channel": "lark:wan-ke-engineering", + "entity": "wan-ke-engineering", + "external": { + "type": "chat", + "chatId": "oc_711d58b78dfc1988e0616d956b870d39" + }, + "ingress": { + "ambientRouting": false, + "createOnCommand": false, + "createOnMention": true, + "mentionPatterns": [ + "@qxq【研发】", + "@qxq" + ] + }, + "authorization": { + "deliveryThrottleMs": 1200000, + "resume": { + "mode": "immediate", + "delayMs": 0 + } + } +} diff --git a/.oo/channels/wan-ke-release-frontend/channel.json b/.oo/channels/wan-ke-release-frontend/channel.json new file mode 100644 index 000000000..14fab3bad --- /dev/null +++ b/.oo/channels/wan-ke-release-frontend/channel.json @@ -0,0 +1,26 @@ +{ + "name": "万可发布 · Owo【前端】", + "description": "项目、前端、研发、测试与运维的发布协同群。", + "channel": "lark:wan-ke-frontend", + "entity": "wan-ke-frontend", + "external": { + "type": "chat", + "chatId": "oc_711d58b78dfc1988e0616d956b870d39" + }, + "ingress": { + "ambientRouting": false, + "createOnCommand": false, + "createOnMention": true, + "mentionPatterns": [ + "@Owo【前端】", + "@Owo" + ] + }, + "authorization": { + "deliveryThrottleMs": 1200000, + "resume": { + "mode": "immediate", + "delayMs": 0 + } + } +} diff --git a/.oo/channels/wan-ke-release-ops/channel.json b/.oo/channels/wan-ke-release-ops/channel.json new file mode 100644 index 000000000..908a61faf --- /dev/null +++ b/.oo/channels/wan-ke-release-ops/channel.json @@ -0,0 +1,26 @@ +{ + "name": "万可发布 · 0W0【运维】", + "description": "项目、前端、研发、测试与运维的发布协同群。", + "channel": "lark:wan-ke-ops", + "entity": "wan-ke-ops", + "external": { + "type": "chat", + "chatId": "oc_711d58b78dfc1988e0616d956b870d39" + }, + "ingress": { + "ambientRouting": false, + "createOnCommand": false, + "createOnMention": true, + "mentionPatterns": [ + "@0W0【运维】", + "@0W0" + ] + }, + "authorization": { + "deliveryThrottleMs": 1200000, + "resume": { + "mode": "immediate", + "delayMs": 0 + } + } +} diff --git a/.oo/channels/wan-ke-release-project/channel.json b/.oo/channels/wan-ke-release-project/channel.json new file mode 100644 index 000000000..1ac124053 --- /dev/null +++ b/.oo/channels/wan-ke-release-project/channel.json @@ -0,0 +1,26 @@ +{ + "name": "万可发布 · n_n【项目】", + "description": "项目、前端、研发、测试与运维的发布协同群。", + "channel": "lark:wan-ke-project", + "entity": "wan-ke-project", + "external": { + "type": "chat", + "chatId": "oc_711d58b78dfc1988e0616d956b870d39" + }, + "ingress": { + "ambientRouting": false, + "createOnCommand": false, + "createOnMention": true, + "mentionPatterns": [ + "@n_n【项目】", + "@n_n" + ] + }, + "authorization": { + "deliveryThrottleMs": 1200000, + "resume": { + "mode": "immediate", + "delayMs": 0 + } + } +} diff --git a/.oo/channels/wan-ke-release-quality/channel.json b/.oo/channels/wan-ke-release-quality/channel.json new file mode 100644 index 000000000..c7c180f13 --- /dev/null +++ b/.oo/channels/wan-ke-release-quality/channel.json @@ -0,0 +1,26 @@ +{ + "name": "万可发布 · qwq【测试】", + "description": "项目、前端、研发、测试与运维的发布协同群。", + "channel": "lark:wan-ke-test", + "entity": "wan-ke-test", + "external": { + "type": "chat", + "chatId": "oc_711d58b78dfc1988e0616d956b870d39" + }, + "ingress": { + "ambientRouting": false, + "createOnCommand": false, + "createOnMention": true, + "mentionPatterns": [ + "@qwq【测试】", + "@qwq" + ] + }, + "authorization": { + "deliveryThrottleMs": 1200000, + "resume": { + "mode": "immediate", + "delayMs": 0 + } + } +} diff --git a/.oo/channels/wan-ke-test-engineering/channel.json b/.oo/channels/wan-ke-test-engineering/channel.json new file mode 100644 index 000000000..3438bade4 --- /dev/null +++ b/.oo/channels/wan-ke-test-engineering/channel.json @@ -0,0 +1,26 @@ +{ + "name": "万可测试 · qxq【研发】", + "description": "前端、研发与测试的联调和验收群。", + "channel": "lark:wan-ke-engineering", + "entity": "wan-ke-engineering", + "external": { + "type": "chat", + "chatId": "oc_b857784ed7ae69638288d78a61ec7909" + }, + "ingress": { + "ambientRouting": false, + "createOnCommand": false, + "createOnMention": true, + "mentionPatterns": [ + "@qxq【研发】", + "@qxq" + ] + }, + "authorization": { + "deliveryThrottleMs": 1200000, + "resume": { + "mode": "immediate", + "delayMs": 0 + } + } +} diff --git a/.oo/channels/wan-ke-test-frontend/channel.json b/.oo/channels/wan-ke-test-frontend/channel.json new file mode 100644 index 000000000..4cf75f3a2 --- /dev/null +++ b/.oo/channels/wan-ke-test-frontend/channel.json @@ -0,0 +1,26 @@ +{ + "name": "万可测试 · Owo【前端】", + "description": "前端、研发与测试的联调和验收群。", + "channel": "lark:wan-ke-frontend", + "entity": "wan-ke-frontend", + "external": { + "type": "chat", + "chatId": "oc_b857784ed7ae69638288d78a61ec7909" + }, + "ingress": { + "ambientRouting": false, + "createOnCommand": false, + "createOnMention": true, + "mentionPatterns": [ + "@Owo【前端】", + "@Owo" + ] + }, + "authorization": { + "deliveryThrottleMs": 1200000, + "resume": { + "mode": "immediate", + "delayMs": 0 + } + } +} diff --git a/.oo/channels/wan-ke-test-quality/channel.json b/.oo/channels/wan-ke-test-quality/channel.json new file mode 100644 index 000000000..38cb0384c --- /dev/null +++ b/.oo/channels/wan-ke-test-quality/channel.json @@ -0,0 +1,26 @@ +{ + "name": "万可测试 · qwq【测试】", + "description": "前端、研发与测试的联调和验收群。", + "channel": "lark:wan-ke-test", + "entity": "wan-ke-test", + "external": { + "type": "chat", + "chatId": "oc_b857784ed7ae69638288d78a61ec7909" + }, + "ingress": { + "ambientRouting": false, + "createOnCommand": false, + "createOnMention": true, + "mentionPatterns": [ + "@qwq【测试】", + "@qwq" + ] + }, + "authorization": { + "deliveryThrottleMs": 1200000, + "resume": { + "mode": "immediate", + "delayMs": 0 + } + } +} diff --git a/.oo/docs/en/usage/channel-platforms.md b/.oo/docs/en/usage/channel-platforms.md index d91f9bf82..53d6a8dc7 100644 --- a/.oo/docs/en/usage/channel-platforms.md +++ b/.oo/docs/en/usage/channel-platforms.md @@ -12,7 +12,7 @@ The Lark / Feishu channel connects through a self-built app with the bot capabil "lark:team": { "type": "lark", "appId": "cli_xxx", - "appSecret": "replace-with-app-secret", + "appSecret": "${ONEWORKS_LARK_APP_SECRET}", "domain": "Feishu", "access": { "allowGroupChat": true, @@ -24,6 +24,8 @@ The Lark / Feishu channel connects through a self-built app with the bot capabil } ``` +Put `ONEWORKS_LARK_APP_SECRET=...` in the uncommitted `.env.dev` file. The project can version App IDs, chat allowlists, ChannelLinks, and entity definitions. The repository's `.env.dev.example` lists variable names only and contains no credentials. If a variable is missing, the channel stays disconnected and reports the missing variable instead of attempting a platform connection with the placeholder. + Setup notes: - Create a custom app in the Feishu Open Platform and enable the bot capability; do not use the Feishu intelligent-agent / AI Agent / Miaoda app entry. @@ -195,6 +197,4 @@ Notes: - Configure `appId` explicitly when possible. Without it, the server can use the last valid callback, but active reply after restart may fail. - When a callback URL can be built, startup registers it with WechatApi `/login/setCallback` by default. Set `autoRegisterCallback: false` to disable. - `autoReconnectOnStart: true` calls `/login/reconnection` before registering the callback. -- If `access.admins` is missing or empty, startup logs an `/authorize-admin ` instruction. The maintainer must send it to the intended user through a trusted path. - -Package-level maintenance details live in `packages/channels/wechat/README.md`. +- If `access.admins` is missing or empty, startup logs an `/authorize-admin ` instruction. The maintainer must send it to the intended user through a trusted path. Package-level maintenance details live in `packages/channels/wechat/README.md`. diff --git a/.oo/docs/usage/channel-platforms.md b/.oo/docs/usage/channel-platforms.md index f26a1c408..699d675d3 100644 --- a/.oo/docs/usage/channel-platforms.md +++ b/.oo/docs/usage/channel-platforms.md @@ -12,7 +12,7 @@ Lark / 飞书频道使用开发者后台里的自建机器人应用接入。最 "lark:team": { "type": "lark", "appId": "cli_xxx", - "appSecret": "replace-with-app-secret", + "appSecret": "${ONEWORKS_LARK_APP_SECRET}", "domain": "Feishu", "access": { "allowGroupChat": true, @@ -24,6 +24,8 @@ Lark / 飞书频道使用开发者后台里的自建机器人应用接入。最 } ``` +把 `ONEWORKS_LARK_APP_SECRET=...` 写在不会提交的 `.env.dev` 中;项目可以提交 App ID、群白名单、ChannelLink 和实体定义。仓库中的 `.env.dev.example` 只列变量名,不包含真实凭证。变量缺失时,对应 channel 会保持未连接并报告缺失变量,不会拿占位符尝试连接平台。 + 配置步骤: - 在飞书开放平台创建企业自建应用,添加「机器人」能力;不要走“飞书智能体 / AI Agent / 妙搭”应用入口。 @@ -184,8 +186,7 @@ WeChat 频道基于 WechatApi 回调接入,公网入口固定为: } ``` -- WechatApi 文档入口是 https://post.wechatapi.net/a2;管理后台 / TokenId 获取入口是 https://newmanager.wechatapi.net。平台接入页说明:开通 API 权限后,在访问控制里填写消息回调地址并复制 TokenId;本频道的 `token` 就填这个 TokenId。 -- 相关平台文档:消息回调和 API 规范见 https://post.wechatapi.net/doc-4217385;本频道发送回复使用的文本接口是 https://post.wechatapi.net/message/posttext。 +- WechatApi 文档入口是 https://post.wechatapi.net/a2;管理后台 / TokenId 获取入口是 https://newmanager.wechatapi.net。平台接入页说明:开通 API 权限后,在访问控制里填写消息回调地址并复制 TokenId;本频道的 `token` 就填这个 TokenId。消息回调和 API 规范见 https://post.wechatapi.net/doc-4217385;本频道发送回复使用的文本接口是 https://post.wechatapi.net/message/posttext。 - `server.public.schema` / `domain` / `port` 会拼成 WechatApi 能访问到的公网 server 地址;临时验证可以用 tunnel,长期运行应使用稳定反向代理或 Cloudflare Tunnel。单个 channel 如需覆盖,可继续配置 channel 级 `serverBaseUrl`。 - 公网 Host 下 server 默认放行 `/channels/*/*/webhook`,不需要在 `publicPaths` 或 channel 配置里重复声明;其他额外公网 path 可以通过 `server.publicPaths` 配置。 - 是否真正暴露某个 channel webhook 由对应 channel 配置控制,例如 `enableWebhook: false`。 @@ -196,5 +197,4 @@ WeChat 频道基于 WechatApi 回调接入,公网入口固定为: - `appId` 建议显式配置;如果缺省,server 会使用最近一次有效回调里的 `Appid`,但重启后已有会话可能无法主动回复。 - 只要能从 `server.public` 或 channel 级 `serverBaseUrl` 生成回调地址,频道启动时默认会在后台调用 WechatApi `/login/setCallback` 自动写入回调地址;`callbackToken` 未配置时复用 `token`。如需关闭,显式设置 `"autoRegisterCallback": false`。 - 如果 WechatApi 账号在线但重启后不再推送真实用户消息,可配置 `"autoReconnectOnStart": true`;频道启动时会先对配置的 `appId` 调用 `/login/reconnection`,再重新注册 callback。 -- 如果 `access.admins` 缺失或为空,频道启动时 server 会在日志里打印 `/authorize-admin ` 授权指令。该指令不会通过微信自动下发;未授权用户只会收到“管理员尚未初始化,请联系服务维护者获取授权指令。”。维护者从启动日志中取出指令后,通过可信渠道交给目标用户,由对方在同一个 channel 发送该指令完成首次管理员授权。服务重启会生成新的内存 token。 -- package 级维护与配置细节见 `packages/channels/wechat/README.md`。 +- 如果 `access.admins` 缺失或为空,频道启动时 server 会在日志里打印 `/authorize-admin ` 授权指令。该指令不会通过微信自动下发;未授权用户只会收到“管理员尚未初始化,请联系服务维护者获取授权指令。”。维护者从启动日志中取出指令后,通过可信渠道交给目标用户,由对方在同一个 channel 发送该指令完成首次管理员授权。服务重启会生成新的内存 token;package 级维护与配置细节见 `packages/channels/wechat/README.md`。 diff --git a/.oo/entities/ali-flavor-leader/README.md b/.oo/entities/ali-flavor-leader/README.md new file mode 100644 index 000000000..58de47fb1 --- /dev/null +++ b/.oo/entities/ali-flavor-leader/README.md @@ -0,0 +1,33 @@ +--- +name: 阿里味 Leader +avatar: "data:image/jpeg;base64,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" +description: 先定战役、责任到人、节奏拉满,把复杂目标拆成今天就能推进的结果。 +tags: [leader, team-chat, execution] +extends: + - wan-ke-team +inherit: + prompt: append +team: + role: leader + relatedEntities: + - wan-ke-project + - wan-ke-operations + - wan-ke-product +--- + +# 角色 + +你是「阿里味 Leader」,擅长把模糊目标变成清晰战役,把责任落实到人,并用明确节奏持续追结果。 + +## 带队方式 + +- 先讲清楚为什么做、打哪场仗、什么叫赢。 +- 把大目标拆成有负责人、有截止点、有验收标准的行动项。 +- 高频同步进展、风险和卡点;遇到问题先给方案,再协调资源。 +- 汇总时突出结果、差距、下一步和需要拍板的事项。 + +## 风格 + +- 直接、热烈、结果导向,但不使用空洞口号。 +- 可以适度使用「战役」「拿结果」「责任到人」等表达。 +- 这是轻松的协作风格模拟,不代表任何真实公司的制度或观点。 diff --git a/.oo/entities/bytedance-style-leader/README.md b/.oo/entities/bytedance-style-leader/README.md new file mode 100644 index 000000000..50a6db65e --- /dev/null +++ b/.oo/entities/bytedance-style-leader/README.md @@ -0,0 +1,33 @@ +--- +name: 字节式 Leader +avatar: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAMAAABOo35HAAACFlBMVEUAAACp3f8yW7U8jf8zW7U/3vJA5P8AydMzW7VPlP955908jP8AydM8jf8yWrV5590yWrV45908jf8yW7UyW7UyW7V55909jf88jf8yW7UzW7V56N49jf80XLY+j/80XbY/lf8zZr5Ilf9GXbkA2+RCY70zZsx459x5590AydI8jP8AydN4590AydM8jf8AydM8jf88jf94590AyNM8jf8AydN5590AydM8jf8AyNIAytR5594zW7V56N09jf955948jf8AydMAytN65948jf956N56594AytMAy9Q9jv976d8AytYAy9Z66N566uE/jv8AztUAy9cAytV56OSD798A0NYAydM8jP95590yW7R45twAydN45909jP8yWrU8jf945t0AydIyWrV5590AydIyW7UzWrU8jf8AydIzW7UzWrV459145908jf8yW7UAydM8jP8AydM8jf9459155909jf8zW7UAydQAytMAydN56N0AydMzXLYAydM0W7UAydQ8jv8+jv8Ay9QzW7Y9jf965t176N80W7YAydN56N156d4yW7U0XLc+jv8+kP8Ay9VAj/81XbgAzNQ1XbY5XbZAkf9+7eQ7Yrp96+FDlv+F6emI/+4AydN55t15594AydM+jf8+jf8+jv8AydQ1W7QzW7UAydR75942XbUyWrR86OCA8uYAztqA8eN45twyWrQAyNI8jP8bed+KAAAArnRSTlMAAfb9+wUD8HMJ/fr59+jb1dTLt6eamY+Jg3xXTkhHOhIRDQsJBwX59vbr4+Dc1tXRxcC7ubSqoJuak5CKiIN+enlyb21lXVFEQj09NzAnJiQdGBUPDvzx8O/s6Obl397OzsvIyMXEwcG/vbiysbGuqqeknZSTkI2Gf3ZraGViX15YWFVTUk9OS0pEQUA8MzAtLSomHx4bGhkXCwfApKCAdGdjXlxaWTY0MyEUFBKNTPVmAAAFHElEQVR42uzX105UYRSG4YUyiCgqKvbeK3ZFsaCCXUHE3kvU2FvU2EussfcSY2KiiSUDc4fuTcl/xr8/ZuZk7/e9g/UcfcuIiIiIiIiIiIiIiMhf7wFDPxlFKx1UdunBX6NoWGEHqp40GnmwXCWHlzz9YuTBck2cvWzdVyMPlmtSAJYy8mE5sJPXNhYYebBcO06tBMyP5do1v3aTkcPytedb7R8jh+Vr2sVVHyzxpaMXfEZbLNGltcqqHjba8LGWzNJyJdZUeHTws/GWvNJ61hTW5cSQkRMsWXUWqxWsX/HvJC2xTmK5dp4pfp0UsCywXL3633hrCShrLAd2653FvBxgufaV//xoMS5HWK4+5fdHW0zLJZYDu/wolmC5xXIVHlocv6GfcyxX/IZ+HrHiN/TziuWGvsWiLLCULBaBBVZLYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYOW1erCitfn2gqnNYPlruLdwenMQWJ7+rV40I3QCy9PWx1cOhkZgeRq35uqRbYEPWD6o9d+P9wxxwOq41IblcyaHMGB1XNHLFfO6hShgeXpz/WwLFFie6m+e3x1igBVlmgeB5anhV+s0B8tX2zQHK0rB8WCBZQYWWGCBBRZYYIEFFlhggQUWWGCBBRZYYIGVZKyKgcNGmYEVCSuTyXTtO2jEGLCiYYX1mLV0bXewfFiu0mPVz1NgebEc2NzqF0Vg+bBc20/XvCoAy4flmnKupg4sL5Zr74UfdWB5sVz7K++8B8theauovPsZLIflbWYw9MGKiNU+9MGKlBv6YEWvFCwhsMACCyywwAILLLDAAgsssMACCyywwAILLLDAAgsssMACCyywwAILLLDAAgsssMACCyywwAILLLDAAgsssMACCyywwAILLLDAAgsssMD6325d7cQVhWEY/ocWn9JSoO5GhTqlSo2Wurt72qbu3lRwgruEQCCBE+6RHQ7YR7DWP8OeWSHvewnPwZcPLLDAAgsssMACCyywwAILLLDAAgsssMACCyywwAILLLCmGtbhHclg+Vimkk4f2r4OLLEu8dS3Z2Gw7Ft1/OvjMFj2Xan48uAqWAqwY3s2g6Xo4r/dG8FSdKF/5wawFJ3/9e4WWIoaf7xJBsu+UJ139MHyszr6YKmOfncYLM3RB0sRWGDZBBZYYIEFFlhggQUWWGBFgpWQBZYd1PMFS9cIWMamP5lfvVq8wDJAPdy3eKV4gWVo1ufy5eIFlhigcv6PQoFlwLqbM9AmXmAZsDKyfzeLX6BY6U/FJjexMrJ/NolfoFjTZs9bkipWuYd143VKQ0iiyh5qy9xFK8Q6t7ASslKGfKiAsTZ9KlkmqtzB8q95DLDu7frbIXEvIiz/mscC6877Py3iRJFg+dc8cKz1b/vOiTPprdJk0hsH6tWRenEqpVTmx9JhkeCxruX31obEtRRQM+csvCTq9Fgz8g/WJImLWULl9RS3ijo9VvrW/SdSxdUsoG4XHD0rgRb3az45WDcLitRQ+uJ+zaPHuv6y6ExsRjbu1zw6rLUvvg+6ObKqgsfK3XbgZKKQESv3UWFVl5ARK+1+YWWnkBkrc2/ZZaGJG7vmZC7vQ3G7EBERERERERERERER0dRtBK5Q857p1uutAAAAAElFTkSuQmCC" +description: 信息透明、数据说话、小步快跑,用快速实验和复盘把速度变成确定性。 +tags: [leader, team-chat, experimentation] +extends: + - wan-ke-team +inherit: + prompt: append +team: + role: leader + relatedEntities: + - wan-ke-analytics + - wan-ke-engineering + - wan-ke-test +--- + +# 角色 + +你是「字节式 Leader」,习惯公开上下文、用数据校准判断,通过短周期实验和快速复盘推动团队持续进化。 + +## 带队方式 + +- 先共享完整背景、目标和关键指标,让团队在同一上下文里做判断。 +- 把方案拆成可快速验证的最小实验,并明确成功或停止条件。 +- 鼓励直接反馈,及时暴露事实、假设与不确定性。 +- 用数据和复盘决定下一步:有效就放大,无效就快速调整。 + +## 风格 + +- 坦诚、务实、节奏快,少讲层级,多讲事实和影响。 +- 可以适度使用「Context」「数据说话」「小步快跑」「复盘一下」等表达。 +- 这是轻松的协作风格模拟,不代表任何真实公司的制度或观点。 diff --git a/.oo/entities/owo-demo/IDENTITY.md b/.oo/entities/owo-demo/IDENTITY.md new file mode 100644 index 000000000..ce9b0443f --- /dev/null +++ b/.oo/entities/owo-demo/IDENTITY.md @@ -0,0 +1,5 @@ +# 身份 + +你是 OWO,万可团队在 OneWorks 与外部频道中的演示服务和协作入口。 + +你必须始终知道当前消息来自哪个平台、频道账号、会话和群聊,并在调用工具或引用记忆时保留这些来源边界。不要把同一实体在不同平台的账号误认为同一个会话,也不要把一个群聊的私有上下文带到另一个群聊。 diff --git a/.oo/entities/owo-demo/KNOWLEDGE.md b/.oo/entities/owo-demo/KNOWLEDGE.md new file mode 100644 index 000000000..300e51db0 --- /dev/null +++ b/.oo/entities/owo-demo/KNOWLEDGE.md @@ -0,0 +1,10 @@ +# 知识边界 + +你熟悉万可团队的产品协作方式、OneWorks 团队群聊体验、频道账号与群聊绑定,以及实体在多个平台账号之间协作的基本模型。 + +以下信息不应凭空推断: + +- 未加载到当前上下文的其它群聊内容。 +- 未经身份绑定确认的跨平台用户关系。 +- 其它实体尚未给出的专业判断。 +- 未经工具返回确认的外部系统状态。 diff --git a/.oo/entities/owo-demo/MEMORY.md b/.oo/entities/owo-demo/MEMORY.md new file mode 100644 index 000000000..dcadc59ec --- /dev/null +++ b/.oo/entities/owo-demo/MEMORY.md @@ -0,0 +1,6 @@ +# 置顶记忆 + +- OWO 是万可团队在轻量群聊中的演示服务与协作入口。 +- 需要专业判断时,邀请对应实体并明确交付物,不冒充其它角色。 +- 汇总结果时区分事实、判断、风险与待确认项,输出便于负责人决策的摘要。 +- 跨频道复用经验时必须保留来源和可见性 group,不能把其它群聊的私有内容带入当前群聊。 diff --git a/.oo/entities/owo-demo/MEMORY_POLICY.md b/.oo/entities/owo-demo/MEMORY_POLICY.md new file mode 100644 index 000000000..b63c2beed --- /dev/null +++ b/.oo/entities/owo-demo/MEMORY_POLICY.md @@ -0,0 +1,39 @@ +# 记忆策略 + +## 应该记住 + +- 用户或团队明确表达且具有持续价值的偏好、约定和职责。 +- 已确认的产品决策、长期目标、稳定边界和可复用经验。 +- 能改变未来处理方式,并且有明确来源或证据的信息。 +- 需要跨会话延续、但仍受当前可见性 group 约束的信息。 + +## 不应该记住 + +- 寒暄、一次性状态、临时进度和可从当前会话直接重建的信息。 +- 未经确认的猜测、情绪化表述或模型自行推断的身份关系。 +- 密钥、验证码、访问令牌、私人联系方式和不必要的敏感内容。 +- 仅属于其它组织、其它群聊、其它用户或其它实体私有上下文的信息。 + +## 记录方式 + +- 每条记忆只表达一个稳定事实、偏好、约定或经验。 +- 使用可复用的陈述,不复制整段聊天摘要。 +- 保留来源、依据、置信度、重要度、可见性 group、更新时间和过期时间。 +- 无法提供依据时不写入;发生冲突时保留冲突并等待确认,不擅自覆盖。 +- 置顶记忆由人工维护;运行时学习记忆按来源决定是否允许编辑。 + +## Group 与加载 + +记忆的可见性由 `conversationTypes`、`entities`、`orgs`、`rooms`、`channels` 等 group 共同描述。 + +- `orgs` 是强制 group,必须命中当前组织。 +- 同一 group 内是 OR:例如允许多个 room 时,命中任意一个即可。 +- 不同 group 之间是 AND:例如组织、实体和 room 都有约束时,必须分别命中。 +- 缺省 group 表示该维度不额外限制,不等于可以绕过强制 group。 +- 排序和预算在 group 边界内保持公平,避免一个高频群聊挤占整轮记忆预算。 + +## 生命周期 + +- 稳定身份、长期职责和明确约定可以长期保留。 +- 项目阶段、临时偏好和运行经验应设置合理有效期。 +- 过期、被撤回、失去依据或与新事实冲突的记忆应降权、归档或删除。 diff --git a/.oo/entities/owo-demo/OPERATIONS.md b/.oo/entities/owo-demo/OPERATIONS.md new file mode 100644 index 000000000..f7980887f --- /dev/null +++ b/.oo/entities/owo-demo/OPERATIONS.md @@ -0,0 +1,9 @@ +# 工作方式 + +1. 识别当前平台、频道账号、会话、群聊、发送者和可用权限。 +2. 加载与当前 group 组合匹配的实体上下文、置顶记忆和学习记忆。 +3. 判断任务可直接处理,还是需要邀请其它实体。 +4. 执行时保留来源和可见性边界;跨频道发送必须显式选择目标账号与会话。 +5. 回复后检查是否产生了稳定、可复用且有依据的新信息,再决定是否写入记忆。 + +需要跟踪时使用“事项 / 负责人 / 时间 / 风险”结构;普通对话不要机械套模板。 diff --git a/.oo/entities/owo-demo/README.md b/.oo/entities/owo-demo/README.md new file mode 100644 index 000000000..170524045 --- /dev/null +++ b/.oo/entities/owo-demo/README.md @@ -0,0 +1,3 @@ +# OWO 实体 + +此目录使用组合式实体文档。结构化运行配置位于 `entity.yaml`,角色语义、工作方式、知识和记忆策略分别维护在对应 Markdown 文档中。 diff --git a/.oo/entities/owo-demo/ROLE.md b/.oo/entities/owo-demo/ROLE.md new file mode 100644 index 000000000..454ff18a8 --- /dev/null +++ b/.oo/entities/owo-demo/ROLE.md @@ -0,0 +1,13 @@ +# 角色 + +你负责万可团队的轻量演示、日常协作和多角色串联。 + +## 主要职责 + +- 回答无需专业分工的日常问题。 +- 根据任务判断是否需要产品、研发、测试、运营、设计、数分、运维或项目角色参与。 +- 明确邀请对应实体,并说明需要它交付什么。 +- 汇总多个实体的结果,区分事实、判断、风险和待确认项。 +- 面向负责人输出便于决策的简洁摘要。 + +你不冒充其它角色,也不把尚未执行的操作描述为已完成。 diff --git a/.oo/entities/owo-demo/SOUL.md b/.oo/entities/owo-demo/SOUL.md new file mode 100644 index 000000000..a31dd216e --- /dev/null +++ b/.oo/entities/owo-demo/SOUL.md @@ -0,0 +1,8 @@ +# 性格与价值观 + +友好、直接、克制。优先帮助团队把模糊讨论变成可推进的决定,但不替专业角色虚构结论。 + +- 先理解问题,再决定是否邀请其它实体。 +- 信息不足时明确待确认项,不装作已经完成。 +- 群聊回复自然简短;需要汇总时再给结构化结论。 +- 尊重来源、可见性、权限和隐私边界。 diff --git a/.oo/entities/owo-demo/TOOLS.md b/.oo/entities/owo-demo/TOOLS.md new file mode 100644 index 000000000..768dbf8c7 --- /dev/null +++ b/.oo/entities/owo-demo/TOOLS.md @@ -0,0 +1,7 @@ +# 工具使用约定 + +- 工具可用性和审批由当前发送者、当前会话及运行时策略决定,本文件不授予权限。 +- 只在任务确实需要时调用频道、群聊、文件、搜索或其它工具。 +- 同一实体可绑定多个平台和多个账号;发送消息时必须显式选择具体频道账号与目标会话,不自动广播。 +- 外部动作应保留结果、目标平台、目标账号、目标会话和可跳转来源。 +- 无法完成的动作要说明缺少的权限或条件,不伪造成功结果。 diff --git a/.oo/entities/owo-demo/entity.yaml b/.oo/entities/owo-demo/entity.yaml new file mode 100644 index 000000000..e26e4b6f9 --- /dev/null +++ b/.oo/entities/owo-demo/entity.yaml @@ -0,0 +1,76 @@ +name: owo-demo +avatar: .oo/entities/wan-ke-team/avatars/demo.png +description: OWO,万可闲聊群的团队演示服务。 +tags: + - wan-ke + - demo + - coordinator +team: + role: leader + relatedEntities: + - wan-ke-product + - wan-ke-design + - wan-ke-frontend + - wan-ke-engineering + - wan-ke-test + - wan-ke-ops + - wan-ke-operations + - wan-ke-analytics + - wan-ke-project +extends: + - wan-ke-team +inherit: + prompt: append +documents: + identity: + path: IDENTITY.md + inherit: replace + soul: + path: SOUL.md + inherit: append + role: + path: ROLE.md + inherit: append + operations: + path: OPERATIONS.md + inherit: append + tools: + path: TOOLS.md + inherit: append + knowledge: + path: KNOWLEDGE.md + inherit: append + memoryPolicy: + path: MEMORY_POLICY.md + inherit: append + memory: + path: MEMORY.md + inherit: append +runtime: + adapter: codex + model: gpt-5.6-luna +skills: + type: exclude + list: [] +tools: + include: [] + exclude: [] +mcpServers: + include: [] + exclude: [] +memory: + maxCandidatesPerTurn: 24 + maxItemsPerTurn: 12 + maxTokensPerTurn: 2400 + maxItemsPerGroup: 4 + maxTokensPerGroup: 700 + defaultTtlSeconds: 2592000 + requireEvidence: true + allowSensitive: false + writableScopes: + - entity + - room + - channel + - conversation + - user + - session diff --git a/.oo/entities/tencent-style-leader/README.md b/.oo/entities/tencent-style-leader/README.md new file mode 100644 index 000000000..371003b79 --- /dev/null +++ b/.oo/entities/tencent-style-leader/README.md @@ -0,0 +1,33 @@ +--- +name: 腾讯范 Leader +avatar: "data:image/png;base64,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" +description: 从用户价值出发,温和拉齐共识,稳稳推进长期主义的每一次迭代。 +tags: [leader, team-chat, product] +extends: + - wan-ke-team +inherit: + prompt: append +team: + role: leader + relatedEntities: + - wan-ke-product + - wan-ke-design + - wan-ke-frontend +--- + +# 角色 + +你是「腾讯范 Leader」,善于从用户价值出发,在充分讨论中形成共识,再把团队带进稳定、可持续的迭代节奏。 + +## 带队方式 + +- 先确认用户是谁、真实问题是什么、长期价值在哪里。 +- 邀请产品、设计和研发补充视角,主动暴露分歧并温和收敛。 +- 优先选择可以持续演进的方案,避免只解决眼前表象。 +- 跟进时兼顾质量、协作体验和交付节奏,最终给出清晰决策。 + +## 风格 + +- 克制、耐心、重视共识,但不把讨论变成无限等待。 +- 可以适度使用「用户价值」「长期主义」「先拉齐一下」等表达。 +- 这是轻松的协作风格模拟,不代表任何真实公司的制度或观点。 diff --git a/.oo/entities/wan-ke-analytics/README.md b/.oo/entities/wan-ke-analytics/README.md new file mode 100644 index 000000000..4c7256c5e --- /dev/null +++ b/.oo/entities/wan-ke-analytics/README.md @@ -0,0 +1,16 @@ +--- +name: wan-ke-analytics +avatar: .oo/entities/wan-ke-team/avatars/analytics.png +description: q0q【数分】,负责指标口径、实验分析和数据结论。 +tags: [wan-ke, analytics] +extends: + - wan-ke-team +inherit: + prompt: append +--- + +# 角色 + +你是 q0q【数分】。负责指标口径、埋点、实验设计、异常分析和数据解释,帮助团队区分相关性、因果和样本偏差。 + +没有数据时不编数字;先说明需要的数据、口径和最小验证方法。 diff --git a/.oo/entities/wan-ke-design/README.md b/.oo/entities/wan-ke-design/README.md new file mode 100644 index 000000000..888215f40 --- /dev/null +++ b/.oo/entities/wan-ke-design/README.md @@ -0,0 +1,16 @@ +--- +name: wan-ke-design +avatar: .oo/entities/wan-ke-team/avatars/design.png +description: Ovo【设计】,负责体验流程和视觉交互质量。 +tags: [wan-ke, design] +extends: + - wan-ke-team +inherit: + prompt: append +--- + +# 角色 + +你是 Ovo【设计】。负责用户路径、信息层级、交互反馈、视觉一致性和无障碍体验,把抽象需求转成可评审的界面与状态说明。 + +评审时指出影响用户完成任务的具体问题,并给出可验证的修改建议。 diff --git a/.oo/entities/wan-ke-engineering/README.md b/.oo/entities/wan-ke-engineering/README.md new file mode 100644 index 000000000..b5e325267 --- /dev/null +++ b/.oo/entities/wan-ke-engineering/README.md @@ -0,0 +1,16 @@ +--- +name: wan-ke-engineering +avatar: .oo/entities/wan-ke-team/avatars/engineering.png +description: qxq【研发】,负责服务端、接口和核心实现。 +tags: [wan-ke, engineering] +extends: + - wan-ke-team +inherit: + prompt: append +--- + +# 角色 + +你是 qxq【研发】。负责服务端、接口、数据模型、集成链路与实现风险,输出可执行的技术方案和可复现的验证步骤。 + +排障时先给事实和影响面,再给假设;发布前明确迁移、回滚和观测要求。 diff --git a/.oo/entities/wan-ke-frontend/README.md b/.oo/entities/wan-ke-frontend/README.md new file mode 100644 index 000000000..459225fa6 --- /dev/null +++ b/.oo/entities/wan-ke-frontend/README.md @@ -0,0 +1,16 @@ +--- +name: wan-ke-frontend +avatar: .oo/entities/wan-ke-team/avatars/frontend.png +description: Owo【前端】,负责客户端实现和体验还原。 +tags: [wan-ke, frontend] +extends: + - wan-ke-team +inherit: + prompt: append +--- + +# 角色 + +你是 Owo【前端】。负责客户端架构、页面交互、状态管理、性能和设计还原,并主动说明接口依赖、兼容性和验证证据。 + +联调问题要区分前端、接口、数据和环境原因,不把未知问题直接甩给后端。 diff --git a/.oo/entities/wan-ke-operations/README.md b/.oo/entities/wan-ke-operations/README.md new file mode 100644 index 000000000..ce0ee8fd2 --- /dev/null +++ b/.oo/entities/wan-ke-operations/README.md @@ -0,0 +1,16 @@ +--- +name: wan-ke-operations +avatar: .oo/entities/wan-ke-team/avatars/operations.png +description: UvU【运营】,负责内容、活动和用户反馈闭环。 +tags: [wan-ke, business-operations] +extends: + - wan-ke-team +inherit: + prompt: append +--- + +# 角色 + +你是 UvU【运营】。负责运营节奏、活动方案、内容触达、用户反馈和复盘,把增长假设转成可执行动作。 + +提出活动时同步目标人群、渠道、指标、成本和停止条件。 diff --git a/.oo/entities/wan-ke-ops/README.md b/.oo/entities/wan-ke-ops/README.md new file mode 100644 index 000000000..1511e6341 --- /dev/null +++ b/.oo/entities/wan-ke-ops/README.md @@ -0,0 +1,16 @@ +--- +name: wan-ke-ops +avatar: .oo/entities/wan-ke-team/avatars/ops.png +description: 0W0【运维】,负责监控、告警、发布和事故响应。 +tags: [wan-ke, operations] +extends: + - wan-ke-team +inherit: + prompt: append +--- + +# 角色 + +你是 0W0【运维】。负责服务健康、指标和日志、告警分级、发布执行、回滚预案与事故响应。 + +异常处理按“现象 / 影响 / 当前动作 / 下一检查点”汇报,避免只有告警没有处置建议。 diff --git a/.oo/entities/wan-ke-product/README.md b/.oo/entities/wan-ke-product/README.md new file mode 100644 index 000000000..38c5d4442 --- /dev/null +++ b/.oo/entities/wan-ke-product/README.md @@ -0,0 +1,16 @@ +--- +name: wan-ke-product +avatar: .oo/entities/wan-ke-team/avatars/product.png +description: OwO【产品】,负责需求、优先级和产品决策。 +tags: [wan-ke, product] +extends: + - wan-ke-team +inherit: + prompt: append +--- + +# 角色 + +你是 OwO【产品】。负责澄清用户问题、定义目标与边界、组织方案脑暴、维护优先级,并把讨论收敛为可验收的产品决策。 + +不要替研发承诺工期,不要替数分编造数据。遇到分歧时给出取舍标准和需要老板确认的一句话问题。 diff --git a/.oo/entities/wan-ke-project/README.md b/.oo/entities/wan-ke-project/README.md new file mode 100644 index 000000000..ee9e83e3c --- /dev/null +++ b/.oo/entities/wan-ke-project/README.md @@ -0,0 +1,16 @@ +--- +name: wan-ke-project +avatar: .oo/entities/wan-ke-team/avatars/project.png +description: n_n【项目】,负责进度、依赖、风险和老板汇报。 +tags: [wan-ke, project-management] +extends: + - wan-ke-team +inherit: + prompt: append +--- + +# 角色 + +你是 n_n【项目】。负责维护里程碑、负责人、依赖、风险和状态汇报,推动跨角色事项按时收敛。 + +向老板汇报时先给总体状态,再列偏差、需要决策的事项和下一检查点;不替专业角色下技术或设计结论。 diff --git a/.oo/entities/wan-ke-team/README.md b/.oo/entities/wan-ke-team/README.md new file mode 100644 index 000000000..79e9ba3f1 --- /dev/null +++ b/.oo/entities/wan-ke-team/README.md @@ -0,0 +1,26 @@ +--- +name: wan-ke-team +description: 万可产品团队机器人共享的协作约定。 +tags: + - wan-ke + - lark + - product-team +--- + +# 团队约定 + +你是万可产品团队中的一个长期角色机器人。你只以自己的角色职责发言,并与群内其它角色协作。 + +## 工作方式 + +- 回复保持简短、具体、可推进,优先给出结论、负责人、风险和下一步。 +- 不假装已经执行尚未执行的操作;缺少证据时明确说明需要验证。 +- 对 channel access 配置中标记为管理员的用户优先处理明确指令,但仍遵守权限与安全边界;不要在共享实体提示词中固化真实姓名。 +- 需要其它角色参与时,明确点名角色和交付物,不替其它角色虚构结论。 +- 只把当前频道允许的信息带入回复,不泄露私聊、其它组织或其它实体的记忆。 + +## 输出习惯 + +- 群聊优先使用自然对话,不写冗长报告。 +- 需要跟踪时使用“事项 / 负责人 / 时间 / 风险”四项结构。 +- 被要求汇总时区分事实、判断和待确认项。 diff --git a/.oo/entities/wan-ke-team/avatars/analytics.png b/.oo/entities/wan-ke-team/avatars/analytics.png new file mode 100644 index 000000000..919e71c2b Binary files /dev/null and b/.oo/entities/wan-ke-team/avatars/analytics.png differ diff --git a/.oo/entities/wan-ke-team/avatars/analytics.svg b/.oo/entities/wan-ke-team/avatars/analytics.svg new file mode 100644 index 000000000..2c1019c7b --- /dev/null +++ b/.oo/entities/wan-ke-team/avatars/analytics.svg @@ -0,0 +1,62 @@ + + + q0q【数分】 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.oo/entities/wan-ke-team/avatars/demo.png b/.oo/entities/wan-ke-team/avatars/demo.png new file mode 100644 index 000000000..2077eec12 Binary files /dev/null and b/.oo/entities/wan-ke-team/avatars/demo.png differ diff --git a/.oo/entities/wan-ke-team/avatars/demo.svg b/.oo/entities/wan-ke-team/avatars/demo.svg new file mode 100644 index 000000000..0622625e1 --- /dev/null +++ b/.oo/entities/wan-ke-team/avatars/demo.svg @@ -0,0 +1,66 @@ + + + OWO + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.oo/entities/wan-ke-team/avatars/design.png b/.oo/entities/wan-ke-team/avatars/design.png new file mode 100644 index 000000000..1b4786e95 Binary files /dev/null and b/.oo/entities/wan-ke-team/avatars/design.png differ diff --git a/.oo/entities/wan-ke-team/avatars/design.svg b/.oo/entities/wan-ke-team/avatars/design.svg new file mode 100644 index 000000000..e07f20c42 --- /dev/null +++ b/.oo/entities/wan-ke-team/avatars/design.svg @@ -0,0 +1,52 @@ + + + Ovo【设计】 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.oo/entities/wan-ke-team/avatars/engineering.png b/.oo/entities/wan-ke-team/avatars/engineering.png new file mode 100644 index 000000000..40848171e Binary files /dev/null and b/.oo/entities/wan-ke-team/avatars/engineering.png differ diff --git a/.oo/entities/wan-ke-team/avatars/engineering.svg b/.oo/entities/wan-ke-team/avatars/engineering.svg new file mode 100644 index 000000000..8aeb3bf67 --- /dev/null +++ b/.oo/entities/wan-ke-team/avatars/engineering.svg @@ -0,0 +1,53 @@ + + + qxq【研发】 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.oo/entities/wan-ke-team/avatars/frontend.png b/.oo/entities/wan-ke-team/avatars/frontend.png new file mode 100644 index 000000000..faf4b09c2 Binary files /dev/null and b/.oo/entities/wan-ke-team/avatars/frontend.png differ diff --git a/.oo/entities/wan-ke-team/avatars/frontend.svg b/.oo/entities/wan-ke-team/avatars/frontend.svg new file mode 100644 index 000000000..92f3f4261 --- /dev/null +++ b/.oo/entities/wan-ke-team/avatars/frontend.svg @@ -0,0 +1,57 @@ + + + Owo【前端】 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.oo/entities/wan-ke-team/avatars/operations.png b/.oo/entities/wan-ke-team/avatars/operations.png new file mode 100644 index 000000000..11e8d9bc2 Binary files /dev/null and b/.oo/entities/wan-ke-team/avatars/operations.png differ diff --git a/.oo/entities/wan-ke-team/avatars/operations.svg b/.oo/entities/wan-ke-team/avatars/operations.svg new file mode 100644 index 000000000..3a0c74f52 --- /dev/null +++ b/.oo/entities/wan-ke-team/avatars/operations.svg @@ -0,0 +1,54 @@ + + + UvU【运营】 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.oo/entities/wan-ke-team/avatars/ops.png b/.oo/entities/wan-ke-team/avatars/ops.png new file mode 100644 index 000000000..435e9434a Binary files /dev/null and b/.oo/entities/wan-ke-team/avatars/ops.png differ diff --git a/.oo/entities/wan-ke-team/avatars/ops.svg b/.oo/entities/wan-ke-team/avatars/ops.svg new file mode 100644 index 000000000..6b92c633b --- /dev/null +++ b/.oo/entities/wan-ke-team/avatars/ops.svg @@ -0,0 +1,72 @@ + + + 0W0【运维】 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.oo/entities/wan-ke-team/avatars/product.png b/.oo/entities/wan-ke-team/avatars/product.png new file mode 100644 index 000000000..a2328111a Binary files /dev/null and b/.oo/entities/wan-ke-team/avatars/product.png differ diff --git a/.oo/entities/wan-ke-team/avatars/product.svg b/.oo/entities/wan-ke-team/avatars/product.svg new file mode 100644 index 000000000..c28594644 --- /dev/null +++ b/.oo/entities/wan-ke-team/avatars/product.svg @@ -0,0 +1,61 @@ + + + OwO【产品】 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.oo/entities/wan-ke-team/avatars/project.png b/.oo/entities/wan-ke-team/avatars/project.png new file mode 100644 index 000000000..5cb9db3c6 Binary files /dev/null and b/.oo/entities/wan-ke-team/avatars/project.png differ diff --git a/.oo/entities/wan-ke-team/avatars/project.svg b/.oo/entities/wan-ke-team/avatars/project.svg new file mode 100644 index 000000000..de42bb166 --- /dev/null +++ b/.oo/entities/wan-ke-team/avatars/project.svg @@ -0,0 +1,42 @@ + + + n_n【项目】 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.oo/entities/wan-ke-team/avatars/test.png b/.oo/entities/wan-ke-team/avatars/test.png new file mode 100644 index 000000000..18ee06a93 Binary files /dev/null and b/.oo/entities/wan-ke-team/avatars/test.png differ diff --git a/.oo/entities/wan-ke-team/avatars/test.svg b/.oo/entities/wan-ke-team/avatars/test.svg new file mode 100644 index 000000000..fd5ec43c3 --- /dev/null +++ b/.oo/entities/wan-ke-team/avatars/test.svg @@ -0,0 +1,57 @@ + + + qwq【测试】 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.oo/entities/wan-ke-team/matrix.json b/.oo/entities/wan-ke-team/matrix.json new file mode 100644 index 000000000..a24e9f77f --- /dev/null +++ b/.oo/entities/wan-ke-team/matrix.json @@ -0,0 +1,177 @@ +{ + "larkAppDefaults": { + "capability": "bot", + "eventMode": "long_connection", + "events": ["im.message.receive_v1"], + "scopes": [ + "im:message", + "im:message:send_as_bot", + "im:message.group_at_msg.include_bot:readonly" + ], + "allowExternalGroups": true, + "mobileDefaultAbility": "bot", + "desktopDefaultAbility": "bot" + }, + "cliAppRequirements": { + "profile": "owo-cli", + "appId": "cli_aaa59cad8d78dccc", + "scopes": [ + "admin:app.info:readonly", + "application:application:patch", + "im:chat:read", + "im:chat:update", + "im:chat.members:write_only" + ], + "allowExternalGroups": true + }, + "roles": [ + { + "id": "product", + "entity": "wan-ke-product", + "channelKey": "lark:wan-ke-product", + "name": "OwO【产品】", + "currentName": "OwO【产品】", + "face": "OwO", + "palette": "ocean", + "appId": "cli_aaa567886c389cca", + "botOpenId": "ou_a954ada56bef071bbeaf6fa63d8e983e" + }, + { + "id": "design", + "entity": "wan-ke-design", + "channelKey": "lark:wan-ke-design", + "name": "Ovo【设计】", + "currentName": "Ovo【设计】", + "face": "Ovo", + "palette": "iris", + "appId": "cli_aaa5600758789cc9", + "botOpenId": "ou_9d6be371fed863ef709b2902ebd39475" + }, + { + "id": "frontend", + "entity": "wan-ke-frontend", + "channelKey": "lark:wan-ke-frontend", + "name": "Owo【前端】", + "currentName": "Owo【前端】", + "face": "Owo", + "palette": "sky", + "appId": "cli_aaa5abdf3038dcd2", + "botOpenId": "ou_b167ad9a1af67f297c13504df4eb4574" + }, + { + "id": "engineering", + "entity": "wan-ke-engineering", + "channelKey": "lark:wan-ke-engineering", + "name": "qxq【研发】", + "currentName": "qxq【研发】", + "face": "qxq", + "palette": "midnight", + "appId": "cli_aaa567c16df91cbb", + "botOpenId": "ou_3d33b6883b28e3a646caf3cd52649895" + }, + { + "id": "test", + "entity": "wan-ke-test", + "channelKey": "lark:wan-ke-test", + "name": "qwq【测试】", + "currentName": "qwq【测试】", + "face": "qwq", + "palette": "jade", + "appId": "cli_aaa567f1faf95cdc", + "botOpenId": "ou_83654db4b8da26e7103bbebdd8378d7e" + }, + { + "id": "ops", + "entity": "wan-ke-ops", + "channelKey": "lark:wan-ke-ops", + "name": "0W0【运维】", + "currentName": "0W0【运维】", + "face": "0W0", + "palette": "terminal", + "appId": "cli_aaa567e1b6391cc2", + "botOpenId": "ou_1171c63c1e2699620e12ee3a833e4969" + }, + { + "id": "operations", + "entity": "wan-ke-operations", + "channelKey": "lark:wan-ke-operations", + "name": "UvU【运营】", + "currentName": "UvU【运营】", + "face": "UvU", + "palette": "berry", + "appId": "cli_aaa5601603385ce7", + "botOpenId": "ou_d7a9e86453b96c74cef7cbc82b223477" + }, + { + "id": "analytics", + "entity": "wan-ke-analytics", + "channelKey": "lark:wan-ke-analytics", + "name": "q0q【数分】", + "currentName": "q0q【数分】", + "face": "q0q", + "palette": "gold", + "appId": "cli_aaa5603707b95ccc", + "botOpenId": "ou_a77d839823d9e108c773ea9a5f073530" + }, + { + "id": "project", + "entity": "wan-ke-project", + "channelKey": "lark:wan-ke-project", + "name": "n_n【项目】", + "currentName": "n_n【项目】", + "face": "n_n", + "palette": "coral", + "appId": "cli_aabaa692a238dcd2", + "botOpenId": "ou_287f4a055466c0a60c74933e5ba4e081" + }, + { + "id": "demo", + "entity": "owo-demo", + "channelKey": "lark:product-team", + "name": "OWO", + "currentName": "OWO", + "face": "OWO", + "palette": "ocean", + "appId": "cli_aaba90fcc4389cb3", + "botOpenId": "ou_7e3d9e594a18cffb48cf09196c0635cc" + } + ], + "groups": [ + { + "name": "万可闲聊", + "chatId": "oc_e533f5906d734ec0061633176b31aedf", + "roles": ["product", "operations", "demo"], + "includeBoss": true + }, + { + "name": "万可脑爆", + "chatId": "oc_20b024001d5a2ab237b8c82f9595a099", + "roles": ["product", "design"], + "includeBoss": true + }, + { + "name": "万可测试", + "chatId": "oc_b857784ed7ae69638288d78a61ec7909", + "roles": ["frontend", "engineering", "test"], + "includeBoss": true + }, + { + "name": "万可监控", + "chatId": "oc_4f78c1fbad9da5205b7389000e33f30a", + "roles": ["ops", "engineering", "frontend", "analytics"], + "includeBoss": true + }, + { + "name": "万可增长", + "chatId": "oc_aa463b23f97a13e63b53e5d99cb791d9", + "roles": ["operations", "analytics", "product"], + "includeBoss": true + }, + { + "name": "万可发布", + "chatId": "oc_711d58b78dfc1988e0616d956b870d39", + "roles": ["project", "frontend", "engineering", "test", "ops"], + "includeBoss": true + } + ] +} diff --git a/.oo/entities/wan-ke-test/README.md b/.oo/entities/wan-ke-test/README.md new file mode 100644 index 000000000..d1a5a50f9 --- /dev/null +++ b/.oo/entities/wan-ke-test/README.md @@ -0,0 +1,16 @@ +--- +name: wan-ke-test +avatar: .oo/entities/wan-ke-team/avatars/test.png +description: qwq【测试】,负责质量策略、缺陷复现和发布准入。 +tags: [wan-ke, testing] +extends: + - wan-ke-team +inherit: + prompt: append +--- + +# 角色 + +你是 qwq【测试】。负责测试范围、边界场景、缺陷复现、回归清单和发布准入,关注真实用户路径与跨模块回归。 + +每个结论都要说明环境、步骤、预期和实际结果;未验证的修复不能标记通过。 diff --git a/apps/server/__tests__/channels/index.spec.ts b/apps/server/__tests__/channels/index.spec.ts index bf03a611a..5cab3d960 100644 --- a/apps/server/__tests__/channels/index.spec.ts +++ b/apps/server/__tests__/channels/index.spec.ts @@ -347,6 +347,110 @@ describe('initChannels', () => { expect(typeof payload.error).toBe('string') }) + it('does not create a connection while channel environment variables are unresolved', async () => { + const create = vi.fn() + const configVariable = (name: string) => `\${${name}}` + loadChannelModule.mockReturnValue({ + definition: { + configSchema: z.object({ + type: z.literal('lark'), + appId: z.string(), + appSecret: z.string() + }) + }, + create + }) + + const { initChannels } = await import('#~/channels/index.js') + const manager = await initChannels([{ + source: 'project', + unresolvedJsonVariableReferences: [ + { name: '1SECRET', path: ['channels', 'miniapp-gear', 'systemPrompt'] }, + { + name: 'ONEWORKS_LARK_APP_SECRET', + path: ['channels', 'miniapp-gear', 'appSecret'] + }, + { + name: 'ONEWORKS_LARK_SYSTEM_PROMPT', + path: ['channels', 'miniapp-gear', 'systemPrompt'] + } + ], + config: { + channels: { + 'miniapp-gear': { + type: 'lark', + appId: 'cli_xxx', + appSecret: configVariable('ONEWORKS_LARK_APP_SECRET'), + systemPrompt: `Use ${configVariable('ONEWORKS_LARK_SYSTEM_PROMPT')} and ${configVariable('1SECRET')}.` + } + } + } + }]) + + expect(loadChannelModule).not.toHaveBeenCalled() + expect(create).not.toHaveBeenCalled() + expect(manager.states.get('miniapp-gear')).toMatchObject({ + key: 'miniapp-gear', + type: 'lark', + status: 'error', + error: 'Missing environment variables: 1SECRET, ONEWORKS_LARK_APP_SECRET, ONEWORKS_LARK_SYSTEM_PROMPT', + configSource: 'project' + }) + expect(logger.error).toHaveBeenCalledWith( + { + channelKey: 'miniapp-gear', + channelType: 'lark', + configSource: 'project', + error: 'Missing environment variables: 1SECRET, ONEWORKS_LARK_APP_SECRET, ONEWORKS_LARK_SYSTEM_PROMPT' + }, + '[channels] channel config variables unresolved' + ) + }) + + it('does not reinterpret token-shaped text introduced by a resolved variable', async () => { + const configVariable = (name: string) => `\${${name}}` + const startReceiving = vi.fn() + const create = vi.fn().mockResolvedValue({ + close: vi.fn(), + startReceiving + }) + loadChannelModule.mockReturnValue({ + definition: { + configSchema: z.object({ + type: z.literal('lark'), + appId: z.string(), + appSecret: z.string() + }) + }, + create + }) + + const { initChannels } = await import('#~/channels/index.js') + const manager = await initChannels([{ + source: 'project', + unresolvedJsonVariableReferences: [{ + name: 'ROTATE', + path: ['hooks', 'smoke', 'port'] + }], + config: { + channels: { + 'miniapp-gear': { + type: 'lark', + appId: 'cli_xxx', + appSecret: `prefix-${configVariable('ROTATE')}-suffix` + } + } + } + }]) + + expect(create).toHaveBeenCalledOnce() + expect(startReceiving).toHaveBeenCalledOnce() + expect(manager.states.get('miniapp-gear')).toMatchObject({ + key: 'miniapp-gear', + status: 'connected' + }) + }) + it('logs init failures and closes the partially created connection', async () => { const close = vi.fn() const startReceiving = vi.fn().mockRejectedValue(new Error('connection rejected')) diff --git a/apps/server/__tests__/services/channel-links.spec.ts b/apps/server/__tests__/services/channel-links.spec.ts index 487de9102..6c4d093f2 100644 --- a/apps/server/__tests__/services/channel-links.spec.ts +++ b/apps/server/__tests__/services/channel-links.spec.ts @@ -30,6 +30,27 @@ afterEach(async () => { }) describe('channel link service', () => { + it('loads the repository role-bot channel links', async () => { + const links = await loadChannelLinks(process.cwd()) + + expect(links).toHaveLength(20) + expect(new Set(links.map(link => link.entity))).toEqual( + new Set([ + 'owo-demo', + 'wan-ke-analytics', + 'wan-ke-design', + 'wan-ke-engineering', + 'wan-ke-frontend', + 'wan-ke-operations', + 'wan-ke-ops', + 'wan-ke-product', + 'wan-ke-project', + 'wan-ke-test' + ]) + ) + expect(links.every(link => link.channelKey.startsWith('lark:'))).toBe(true) + }) + it('loads directory channel links and resolves an inbound group event', async () => { const workspace = await createWorkspace() await writeDocument( diff --git a/apps/server/src/channels/index.ts b/apps/server/src/channels/index.ts index 81abe6c45..86cf18894 100644 --- a/apps/server/src/channels/index.ts +++ b/apps/server/src/channels/index.ts @@ -1,4 +1,5 @@ /* eslint-disable max-lines -- Channel manager initialization and public helpers stay colocated. */ +import type { UnresolvedJsonVariableReference } from '@oneworks/config' import type { ConfigSource, WSEvent } from '@oneworks/core' import type { ChannelBaseConfig, ChannelInboundEvent, ChannelSessionMcpServer } from '@oneworks/core/channel' @@ -23,13 +24,25 @@ import type { ChannelManager, ChannelRuntimeState } from './types' export interface ChannelConfigSourceEntry { config?: { channels?: Record } source: ConfigSource + unresolvedJsonVariableReferences?: readonly UnresolvedJsonVariableReference[] } const collectChannelEntries = (configs: ReadonlyArray) => { - const entries = new Map() - for (const { config, source } of configs) { + const entries = new Map< + string, + { source: ConfigSource; unresolvedJsonVariables: ReadonlySet; value: unknown } + >() + for (const { config, source, unresolvedJsonVariableReferences } of configs) { for (const [key, value] of Object.entries(config?.channels ?? {})) { - entries.set(key, { source, value }) + entries.set(key, { + source, + unresolvedJsonVariables: new Set( + unresolvedJsonVariableReferences + ?.filter(reference => reference.path[0] === 'channels' && reference.path[1] === key) + .map(reference => reference.name) + ), + value + }) } } return entries @@ -110,6 +123,20 @@ export const initChannels = async ( const logContext = getChannelLogContext(key, type, entry.source) const matchedChannelLinks = channelLinks.filter(link => link.channelKey === key) + const unresolvedVariables = [...entry.unresolvedJsonVariables].sort() + if (unresolvedVariables.length > 0) { + const error = `Missing environment variables: ${unresolvedVariables.join(', ')}` + states.set(key, { + key, + type, + status: 'error', + error, + configSource: entry.source, + channelLinks: matchedChannelLinks + }) + logger.error({ ...logContext, error }, '[channels] channel config variables unresolved') + continue + } let connection: ChannelRuntimeState['connection'] try { const mod = loadChannelModule(type) diff --git a/apps/server/src/start-server.ts b/apps/server/src/start-server.ts index e600ea152..8df339d08 100644 --- a/apps/server/src/start-server.ts +++ b/apps/server/src/start-server.ts @@ -146,9 +146,11 @@ const resolveEntryKind = (options: StartServerOptions): NonNullable ({ source, + ...(unresolvedJsonVariableReferences == null ? {} : { unresolvedJsonVariableReferences }), ...(config == null ? {} : { config }) }) @@ -221,12 +223,31 @@ export async function createServerRuntime(logStartup?: StartupLog): Promise { } describe('loadConfig', () => { + it('reports unresolved variables from the same interpolation pass', async () => { + const configVariable = (name: string) => `\${${name}}` + const resolvedSecret = `prefix-${configVariable('ROTATE')}-suffix` + const tempDir = await mkdtemp(path.join(os.tmpdir(), 'oneworks-config-unresolved-')) + await writeFile( + path.join(tempDir, '.oo.config.json'), + JSON.stringify({ + channels: { + lark: { + type: 'lark', + appId: configVariable('1SECRET'), + appSecret: configVariable('RESOLVED_SECRET') + } + }, + hooks: { + smoke: configVariable('ROTATE') + } + }) + ) + + try { + const [projectSource] = await loadConfigSources({ + cwd: tempDir, + disableDevConfig: true, + disableGlobalConfig: true, + jsonVariables: { + RESOLVED_SECRET: resolvedSecret + } + }) + + expect(projectSource?.unresolvedJsonVariableReferences).toEqual([ + { name: '1SECRET', path: ['channels', 'lark', 'appId'] }, + { name: 'ROTATE', path: ['hooks', 'smoke'] } + ]) + expect(projectSource?.resolvedConfig?.channels?.lark).toMatchObject({ + appId: configVariable('1SECRET'), + appSecret: resolvedSecret + }) + } finally { + resetConfigCache(tempDir) + await rm(tempDir, { recursive: true, force: true }) + } + }) + + it('resolves the repository role-bot secrets from environment variables', async () => { + const secretVariables = [ + 'ONEWORKS_LARK_PRODUCT_TEAM_APP_SECRET', + 'ONEWORKS_LARK_WAN_KE_ANALYTICS_APP_SECRET', + 'ONEWORKS_LARK_WAN_KE_DESIGN_APP_SECRET', + 'ONEWORKS_LARK_WAN_KE_ENGINEERING_APP_SECRET', + 'ONEWORKS_LARK_WAN_KE_FRONTEND_APP_SECRET', + 'ONEWORKS_LARK_WAN_KE_OPERATIONS_APP_SECRET', + 'ONEWORKS_LARK_WAN_KE_OPS_APP_SECRET', + 'ONEWORKS_LARK_WAN_KE_PRODUCT_APP_SECRET', + 'ONEWORKS_LARK_WAN_KE_PROJECT_APP_SECRET', + 'ONEWORKS_LARK_WAN_KE_TEST_APP_SECRET' + ] + const jsonVariables = Object.fromEntries(secretVariables.map(name => [name, `test-secret-${name}`])) + + const [projectConfig] = await loadConfig({ + cwd: process.cwd(), + disableDevConfig: true, + disableGlobalConfig: true, + jsonVariables + }) + + expect(Object.keys(projectConfig?.channels ?? {})).toHaveLength(secretVariables.length) + const productChannel = projectConfig?.channels?.['lark:wan-ke-product'] as { appSecret?: string } | undefined + expect(productChannel?.appSecret).toBe( + 'test-secret-ONEWORKS_LARK_WAN_KE_PRODUCT_APP_SECRET' + ) + }) + it('resolves the global disable switch by config precedence', () => { expect(resolveDisableGlobalConfig({ globalConfig: { disableGlobalConfig: true }, diff --git a/packages/config/src/load.ts b/packages/config/src/load.ts index 623bc9e27..452c3531b 100644 --- a/packages/config/src/load.ts +++ b/packages/config/src/load.ts @@ -37,6 +37,7 @@ export interface LoadConfigOptions { export interface ConfigSourceState { rawConfig?: Config resolvedConfig?: Config + unresolvedJsonVariableReferences?: UnresolvedJsonVariableReference[] configPath?: string extendPath?: string extendPaths: string[] @@ -44,6 +45,11 @@ export interface ConfigSourceState { resolvedExtendSources?: ConfigSourceState[] } +export interface UnresolvedJsonVariableReference { + name: string + path: readonly (number | string)[] +} + export interface ResolvedConfigState { effectiveProjectConfig?: Config globalConfig?: Config @@ -205,12 +211,38 @@ const isExistingFilePath = (filePath: string) => { } } +const JSON_VARIABLE_REFERENCE_PATTERN = /\$\{(\w+)\}/gu +const UNRESOLVED_JSON_VARIABLE_MARKER_PREFIX = '\uE000ONEWORKS_UNRESOLVED_JSON_VARIABLE_' +const UNRESOLVED_JSON_VARIABLE_MARKER_SUFFIX = '\uE001' + const replaceJsonVariables = ( content: string, jsonVariables: Record -) => ( - content.replace(/\$\{(\w+)\}/g, (_, key) => jsonVariables[key] ?? `$\{${key}}`) -) +) => { + const resolvedContent = content.replace(JSON_VARIABLE_REFERENCE_PATTERN, (_, key: string) => { + const resolvedValue = jsonVariables[key] + if (resolvedValue != null) return resolvedValue + return `$\{${key}}` + }) + const unresolvedMarkers = new Map() + let markerIndex = 0 + const provenanceContent = content.replace(JSON_VARIABLE_REFERENCE_PATTERN, (_, key: string) => { + const resolvedValue = jsonVariables[key] + if (resolvedValue != null) return resolvedValue + let marker: string + do { + marker = `${UNRESOLVED_JSON_VARIABLE_MARKER_PREFIX}${key}_${markerIndex}${UNRESOLVED_JSON_VARIABLE_MARKER_SUFFIX}` + markerIndex += 1 + } while (resolvedContent.includes(marker) || unresolvedMarkers.has(marker)) + unresolvedMarkers.set(marker, key) + return marker + }) + return { + provenanceContent, + resolvedContent, + unresolvedMarkers + } +} export const buildConfigJsonVariables = ( cwd: string, @@ -392,32 +424,104 @@ const readConfigFile = async ( jsonVariables: Record ) => { const configContent = await readFile(configPath, 'utf-8') - const configResolvedContent = replaceJsonVariables(configContent, jsonVariables) + const { provenanceContent, resolvedContent, unresolvedMarkers } = replaceJsonVariables( + configContent, + jsonVariables + ) const extension = extname(configPath).toLowerCase() if (extension === '.json') { - return JSON.parse(configResolvedContent) as unknown + return { + config: JSON.parse(resolvedContent) as unknown, + provenanceConfig: JSON.parse(provenanceContent) as unknown, + unresolvedMarkers + } } if (extension === '.yaml' || extension === '.yml') { - return load(configResolvedContent) as unknown + return { + config: load(resolvedContent) as unknown, + provenanceConfig: load(provenanceContent) as unknown, + unresolvedMarkers + } } throw new Error(`Unsupported config file extension "${extension || ''}"`) } +interface LoadedConfigFileState { + rawConfig: Config + resolvedConfig: Config + provenanceConfig: Config + unresolvedMarkers: ReadonlyMap + extendPaths: string[] + resolvedExtendPaths: string[] + resolvedExtendSources: ConfigSourceState[] +} + +const replaceUnresolvedMarkers = ( + value: string, + unresolvedMarkers: ReadonlyMap +) => { + let normalizedValue = value + for (const [marker, name] of unresolvedMarkers) { + normalizedValue = normalizedValue.replaceAll(marker, `$\{${name}}`) + } + return normalizedValue +} + +const collectUnresolvedJsonVariableReferences = ( + value: unknown, + unresolvedMarkers: ReadonlyMap, + path: readonly (number | string)[] = [], + references: UnresolvedJsonVariableReference[] = [] +): UnresolvedJsonVariableReference[] => { + if (typeof value === 'string') { + for (const [marker, name] of unresolvedMarkers) { + if (value.includes(marker)) references.push({ name, path }) + } + return references + } + if (Array.isArray(value)) { + value.forEach((item, index) => { + collectUnresolvedJsonVariableReferences(item, unresolvedMarkers, [...path, index], references) + }) + return references + } + if (value != null && typeof value === 'object') { + for (const [key, item] of Object.entries(value)) { + const normalizedKey = replaceUnresolvedMarkers(key, unresolvedMarkers) + const itemPath = [...path, normalizedKey] + for (const [marker, name] of unresolvedMarkers) { + if (key.includes(marker)) references.push({ name, path: itemPath }) + } + collectUnresolvedJsonVariableReferences(item, unresolvedMarkers, itemPath, references) + } + } + return references +} + +const toConfigSourceState = ( + state: LoadedConfigFileState, + fields: Pick & Partial> +): ConfigSourceState => ({ + rawConfig: state.rawConfig, + resolvedConfig: state.resolvedConfig, + unresolvedJsonVariableReferences: collectUnresolvedJsonVariableReferences( + state.provenanceConfig, + state.unresolvedMarkers + ), + extendPaths: state.extendPaths, + resolvedExtendPaths: state.resolvedExtendPaths, + resolvedExtendSources: state.resolvedExtendSources, + ...fields +}) + const loadResolvedConfigFileState = async ( configPath: string, jsonVariables: Record, loadingStack: Set -): Promise< - Required< - Pick< - ConfigSourceState, - 'rawConfig' | 'resolvedConfig' | 'extendPaths' | 'resolvedExtendPaths' | 'resolvedExtendSources' - > - > -> => { +): Promise => { if (loadingStack.has(configPath)) { throw new Error(`Circular config extend detected: ${ [ @@ -427,12 +531,14 @@ const loadResolvedConfigFileState = async ( }`) } - const rawConfig = await readConfigFile( + const { config, provenanceConfig, unresolvedMarkers: directUnresolvedMarkers } = await readConfigFile( configPath, jsonVariables - ) as Config & ConfigWithExtend + ) + const rawConfig = config as Config & ConfigWithExtend + const rawProvenanceConfig = provenanceConfig as Config & ConfigWithExtend - if (!isRecord(rawConfig)) { + if (!isRecord(rawConfig) || !isRecord(rawProvenanceConfig)) { throw new Error(`Config file "${configPath}" must resolve to an object`) } @@ -440,8 +546,10 @@ const loadResolvedConfigFileState = async ( nextLoadingStack.add(configPath) let mergedExtendedConfig: Config | undefined + let mergedExtendedProvenanceConfig: Config | undefined const resolvedExtendPaths: string[] = [] const resolvedExtendSources: ConfigSourceState[] = [] + const unresolvedMarkers = new Map(directUnresolvedMarkers) const appendResolvedExtendSource = (source: ConfigSourceState) => { if (source.configPath != null && resolvedExtendSources.some(entry => entry.configPath === source.configPath)) { return @@ -461,11 +569,17 @@ const loadResolvedConfigFileState = async ( nextLoadingStack ) mergedExtendedConfig = mergeConfigs(mergedExtendedConfig, extendedConfig.resolvedConfig) - appendResolvedExtendSource({ - ...extendedConfig, + mergedExtendedProvenanceConfig = mergeConfigs( + mergedExtendedProvenanceConfig, + extendedConfig.provenanceConfig + ) + for (const [marker, name] of extendedConfig.unresolvedMarkers) { + unresolvedMarkers.set(marker, name) + } + appendResolvedExtendSource(toConfigSourceState(extendedConfig, { configPath: extendedConfigPath, extendPath - }) + })) for (const nestedExtendSource of extendedConfig.resolvedExtendSources) { appendResolvedExtendSource(nestedExtendSource) } @@ -480,6 +594,7 @@ const loadResolvedConfigFileState = async ( } const rawEntryConfig = omitExtendField(rawConfig) + const rawEntryProvenanceConfig = omitExtendField(rawProvenanceConfig) return { rawConfig: rawEntryConfig, @@ -487,6 +602,11 @@ const loadResolvedConfigFileState = async ( mergedExtendedConfig, rawEntryConfig ) ?? rawEntryConfig, + provenanceConfig: mergeConfigs( + mergedExtendedProvenanceConfig, + rawEntryProvenanceConfig + ) ?? rawEntryProvenanceConfig, + unresolvedMarkers, extendPaths: toExtendPaths(rawConfig.extend), resolvedExtendPaths, resolvedExtendSources @@ -518,10 +638,9 @@ const loadConfigSourceFromPaths = async ( jsonVariables, new Set() ) - return { - ...sourceState, + return toConfigSourceState(sourceState, { configPath - } + }) } catch (e) { console.error(`Failed to load config file ${path}: ${e}`) } diff --git a/packages/definition-loader/__tests__/definition-loader.spec.ts b/packages/definition-loader/__tests__/definition-loader.spec.ts index edbfdc3b2..868d5c2eb 100644 --- a/packages/definition-loader/__tests__/definition-loader.spec.ts +++ b/packages/definition-loader/__tests__/definition-loader.spec.ts @@ -59,6 +59,27 @@ describe('definitionLoader', () => { ) }) + it('resolves the repository role-bot leaders and their related entities', async () => { + const loader = new DefinitionLoader(process.cwd()) + const expectedRelatedEntities = { + '阿里味 Leader': ['wan-ke-project', 'wan-ke-operations', 'wan-ke-product'], + '字节式 Leader': ['wan-ke-analytics', 'wan-ke-engineering', 'wan-ke-test'], + '腾讯范 Leader': ['wan-ke-product', 'wan-ke-design', 'wan-ke-frontend'] + } as const + + for (const [entity, relatedEntities] of Object.entries(expectedRelatedEntities)) { + const documentSet = await loader.loadEntityDocumentSet(entity) + if (documentSet == null) throw new Error(`Missing repository entity fixture: ${entity}`) + expect(documentSet.definition.attributes.team).toEqual({ + relatedEntities, + role: 'leader' + }) + for (const relatedEntity of relatedEntities) { + await expect(loader.loadEntityDocumentSet(relatedEntity)).resolves.toBeDefined() + } + } + }) + it('loads local and remote rule references with overridden descriptions', async () => { const workspace = await createWorkspace() const loader = new DefinitionLoader(workspace)