fix: align WeChat channel modes
This commit is contained in:
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "goodbuddy",
|
"name": "goodbuddy",
|
||||||
"version": "0.8.10",
|
"version": "0.8.11",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "goodbuddy",
|
"name": "goodbuddy",
|
||||||
"version": "0.8.10",
|
"version": "0.8.11",
|
||||||
"license": "UNLICENSED",
|
"license": "UNLICENSED",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@modelcontextprotocol/sdk": "^1.30.0",
|
"@modelcontextprotocol/sdk": "^1.30.0",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "goodbuddy",
|
"name": "goodbuddy",
|
||||||
"version": "0.8.10",
|
"version": "0.8.11",
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "Secure desktop AI workspace with controlled Agent Runtimes",
|
"description": "Secure desktop AI workspace with controlled Agent Runtimes",
|
||||||
"desktopName": "GoodBuddy",
|
"desktopName": "GoodBuddy",
|
||||||
|
|||||||
@@ -226,5 +226,5 @@ export function startEnvironmentChannels(
|
|||||||
export function isReadOnlyChannelMessage(
|
export function isReadOnlyChannelMessage(
|
||||||
message: ChannelInboundText
|
message: ChannelInboundText
|
||||||
): boolean {
|
): boolean {
|
||||||
return message.workMode === 'ask' || message.workMode === 'plan'
|
return message.workMode === 'ask'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ async function waitForSent(
|
|||||||
}
|
}
|
||||||
|
|
||||||
describe('channel contracts', () => {
|
describe('channel contracts', () => {
|
||||||
it('normalizes text, defaults to ask, and strictly refuses execute mode', () => {
|
it('normalizes text, defaults to ask, and refuses non-ask modes', () => {
|
||||||
expect(
|
expect(
|
||||||
channelInboundTextSchema.parse({
|
channelInboundTextSchema.parse({
|
||||||
channel: ' fake ',
|
channel: ' fake ',
|
||||||
@@ -98,6 +98,12 @@ describe('channel contracts', () => {
|
|||||||
workMode: 'execute'
|
workMode: 'execute'
|
||||||
}).success
|
}).success
|
||||||
).toBe(false)
|
).toBe(false)
|
||||||
|
expect(
|
||||||
|
channelInboundTextSchema.safeParse({
|
||||||
|
...inbound(),
|
||||||
|
workMode: 'plan'
|
||||||
|
}).success
|
||||||
|
).toBe(false)
|
||||||
expect(
|
expect(
|
||||||
channelInboundTextSchema.parse({
|
channelInboundTextSchema.parse({
|
||||||
channel: 'fake',
|
channel: 'fake',
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ describe('parseRemoteChannelPrompt', () => {
|
|||||||
workMode: 'execute',
|
workMode: 'execute',
|
||||||
prompt: '请整理下载目录'
|
prompt: '请整理下载目录'
|
||||||
})
|
})
|
||||||
expect(parseRemoteChannelPrompt('总结进展', 'plan')).toEqual({
|
expect(parseRemoteChannelPrompt('总结进展', 'ask')).toEqual({
|
||||||
workMode: 'plan',
|
workMode: 'ask',
|
||||||
prompt: '总结进展'
|
prompt: '总结进展'
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { WorkMode } from '../../shared/assistant-contracts'
|
import type { InteractiveWorkMode } from '../../shared/assistant-contracts'
|
||||||
|
|
||||||
const COMMAND_PATTERN =
|
const COMMAND_PATTERN =
|
||||||
/^\/(?<command>ask|execute|exec)(?=$|[\s::])[\s::]*/iu
|
/^\/(?<command>ask|execute|exec)(?=$|[\s::])[\s::]*/iu
|
||||||
@@ -7,9 +7,9 @@ const CHINESE_PATTERN =
|
|||||||
|
|
||||||
export function parseRemoteChannelPrompt(
|
export function parseRemoteChannelPrompt(
|
||||||
text: string,
|
text: string,
|
||||||
defaultWorkMode: WorkMode
|
defaultWorkMode: InteractiveWorkMode
|
||||||
): {
|
): {
|
||||||
workMode: WorkMode
|
workMode: InteractiveWorkMode
|
||||||
prompt: string
|
prompt: string
|
||||||
} {
|
} {
|
||||||
const value = text.trim()
|
const value = text.trim()
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ const channelMocks = vi.hoisted(() => ({
|
|||||||
conversationType: 'direct' | 'group'
|
conversationType: 'direct' | 'group'
|
||||||
text: string
|
text: string
|
||||||
mentioned: boolean
|
mentioned: boolean
|
||||||
workMode: 'ask' | 'plan'
|
workMode: 'ask'
|
||||||
attachments?: Array<{
|
attachments?: Array<{
|
||||||
name: string
|
name: string
|
||||||
mimeType: string
|
mimeType: string
|
||||||
@@ -276,7 +276,7 @@ vi.mock('./agent/create-runtime', () => runtimeFactoryMocks)
|
|||||||
|
|
||||||
vi.mock('./channels/channel-env', () => ({
|
vi.mock('./channels/channel-env', () => ({
|
||||||
isReadOnlyChannelMessage: (message: { workMode: string }) =>
|
isReadOnlyChannelMessage: (message: { workMode: string }) =>
|
||||||
message.workMode === 'ask' || message.workMode === 'plan',
|
message.workMode === 'ask',
|
||||||
startEnvironmentChannels: vi.fn(
|
startEnvironmentChannels: vi.fn(
|
||||||
(options: { executor: typeof channelMocks.executor }) => {
|
(options: { executor: typeof channelMocks.executor }) => {
|
||||||
channelMocks.executor = options.executor
|
channelMocks.executor = options.executor
|
||||||
@@ -1873,7 +1873,7 @@ describe('registerIpcHandlers agent terminal state', () => {
|
|||||||
await harness.dispose()
|
await harness.dispose()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('bridges channel requests to read-only delegation tasks without approval', async () => {
|
it('bridges channel ask requests to read-only tasks without approval', async () => {
|
||||||
let received:
|
let received:
|
||||||
| {
|
| {
|
||||||
request: {
|
request: {
|
||||||
@@ -1928,9 +1928,9 @@ describe('registerIpcHandlers agent terminal state', () => {
|
|||||||
senderId: 'user-1',
|
senderId: 'user-1',
|
||||||
conversationId: 'conversation-1',
|
conversationId: 'conversation-1',
|
||||||
conversationType: 'direct',
|
conversationType: 'direct',
|
||||||
text: '请制定只读计划',
|
text: '请只读分析',
|
||||||
mentioned: false,
|
mentioned: false,
|
||||||
workMode: 'plan'
|
workMode: 'ask'
|
||||||
},
|
},
|
||||||
new AbortController().signal
|
new AbortController().signal
|
||||||
)
|
)
|
||||||
@@ -1939,8 +1939,8 @@ describe('registerIpcHandlers agent terminal state', () => {
|
|||||||
output: '只读结果'
|
output: '只读结果'
|
||||||
})
|
})
|
||||||
expect(received?.request).toMatchObject({
|
expect(received?.request).toMatchObject({
|
||||||
workMode: 'plan',
|
workMode: 'ask',
|
||||||
prompt: expect.stringContaining('请制定只读计划')
|
prompt: expect.stringContaining('请只读分析')
|
||||||
})
|
})
|
||||||
await expect(
|
await expect(
|
||||||
received?.authorize?.({
|
received?.authorize?.({
|
||||||
@@ -1953,8 +1953,8 @@ describe('registerIpcHandlers agent terminal state', () => {
|
|||||||
expect(harness.assistantDatabase.createTask).toHaveBeenCalledWith(
|
expect(harness.assistantDatabase.createTask).toHaveBeenCalledWith(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
title: '企业微信远程请求',
|
title: '企业微信远程请求',
|
||||||
instructions: '请制定只读计划',
|
instructions: '请只读分析',
|
||||||
workMode: 'plan',
|
workMode: 'ask',
|
||||||
origin: 'delegation'
|
origin: 'delegation'
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|||||||
+2
-6
@@ -1352,9 +1352,7 @@ export function registerIpcHandlers(
|
|||||||
try {
|
try {
|
||||||
parsed = parseRemoteChannelPrompt(
|
parsed = parseRemoteChannelPrompt(
|
||||||
remoteInput,
|
remoteInput,
|
||||||
message.workMode === 'plan'
|
normalizeInteractiveWorkMode(project.defaultWorkMode)
|
||||||
? 'plan'
|
|
||||||
: project.defaultWorkMode
|
|
||||||
)
|
)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return {
|
return {
|
||||||
@@ -1432,9 +1430,7 @@ export function registerIpcHandlers(
|
|||||||
status: `${channelLabel} · ${
|
status: `${channelLabel} · ${
|
||||||
parsed.workMode === 'execute'
|
parsed.workMode === 'execute'
|
||||||
? '执行'
|
? '执行'
|
||||||
: parsed.workMode === 'plan'
|
: '对话'
|
||||||
? '规划'
|
|
||||||
: '对话'
|
|
||||||
}`
|
}`
|
||||||
})
|
})
|
||||||
publishRemoteConversationChange()
|
publishRemoteConversationChange()
|
||||||
|
|||||||
@@ -330,6 +330,11 @@ describe('ChannelSettingsSection', () => {
|
|||||||
const close = await screen.findByRole('button', {
|
const close = await screen.findByRole('button', {
|
||||||
name: '关闭微信绑定'
|
name: '关闭微信绑定'
|
||||||
})
|
})
|
||||||
|
expect(
|
||||||
|
screen.getByText(
|
||||||
|
'请在微信中依次打开“设置 → ClawBot → 开始扫一扫”,扫描下方二维码。二维码不会发送到第三方页面。'
|
||||||
|
)
|
||||||
|
).toBeInTheDocument()
|
||||||
await waitFor(() => expect(close).toHaveFocus())
|
await waitFor(() => expect(close).toHaveFocus())
|
||||||
|
|
||||||
fireEvent.keyDown(document, { key: 'Escape' })
|
fireEvent.keyDown(document, { key: 'Escape' })
|
||||||
|
|||||||
@@ -681,7 +681,9 @@ function WeixinQrDialog({
|
|||||||
<header>
|
<header>
|
||||||
<div>
|
<div>
|
||||||
<strong id="channel-qr-title">绑定微信 ClawBot</strong>
|
<strong id="channel-qr-title">绑定微信 ClawBot</strong>
|
||||||
<small>请使用个人微信扫码。二维码不会发送到第三方页面。</small>
|
<small>
|
||||||
|
请在微信中依次打开“设置 → ClawBot → 开始扫一扫”,扫描下方二维码。二维码不会发送到第三方页面。
|
||||||
|
</small>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
aria-label="关闭微信绑定"
|
aria-label="关闭微信绑定"
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ const channelIdentifierSchema = z
|
|||||||
.min(1)
|
.min(1)
|
||||||
.max(CHANNEL_LIMITS.maximumIdentityLength)
|
.max(CHANNEL_LIMITS.maximumIdentityLength)
|
||||||
|
|
||||||
export const channelWorkModeSchema = z.enum(['ask', 'plan'])
|
export const channelWorkModeSchema = z.literal('ask')
|
||||||
export type ChannelWorkMode = z.infer<typeof channelWorkModeSchema>
|
export type ChannelWorkMode = z.infer<typeof channelWorkModeSchema>
|
||||||
|
|
||||||
const attachmentBase64Schema = z
|
const attachmentBase64Schema = z
|
||||||
|
|||||||
Reference in New Issue
Block a user