@puredesktop/puredesktop-ui-bridge 2.1.8 → 2.1.9
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 +35 -5
- package/src/agents/mcpServerPresets.test.ts +105 -0
- package/src/agents/mcpServerPresets.ts +380 -0
- package/src/agents/runtime/index.ts +7 -0
- package/src/agents/runtime/mapAgentSnapshot.ts +11 -8
- package/src/agents/runtime/pendingAsks.test.ts +117 -0
- package/src/agents/runtime/pendingAsks.ts +110 -0
- package/src/bridge/agentScopeDefaults.ts +65 -0
- package/src/bridge/agentTypes.ts +36 -2
- package/src/bridge/agents.d.mts +6 -0
- package/src/bridge/agents.mjs +10 -0
- package/src/bridge/agents.ts +4 -0
- package/src/bridge/credentials.d.mts +62 -0
- package/src/bridge/credentials.mjs +53 -0
- package/src/bridge/events.d.mts +1 -0
- package/src/bridge/events.mjs +1 -0
- package/src/bridge/googleAuth.ts +7 -152
- package/src/bridge/mcpClient.d.mts +74 -0
- package/src/bridge/mcpClient.mjs +79 -0
- package/src/bridge/methods.d.mts +22 -0
- package/src/bridge/methods.mjs +23 -0
- package/src/bridge/notifications.d.mts +12 -0
- package/src/bridge/notifications.mjs +36 -0
- package/src/bridge/operations.d.mts +44 -0
- package/src/bridge/operations.mjs +40 -0
- package/src/bridge/react/usePlatformAgentSessionDrive.test.tsx +29 -10
- package/src/bridge/react/usePlatformAgentSessionDrive.tsx +10 -81
- package/src/bridge/react/usePlatformAppSettings.test.tsx +105 -105
- package/src/bridge/react/usePlatformAppSettings.tsx +104 -104
- package/src/bridge/react/usePlatformJsonStore.test.tsx +152 -152
- package/src/bridge/react/usePlatformJsonStore.tsx +149 -149
- package/src/bridge/react/usePlatformOperations.tsx +75 -0
- package/src/bridge/storage.d.mts +17 -4
- package/src/bridge/storage.mjs +6 -3
- package/src/components/agents/AgentComposer.tsx +10 -6
- package/src/components/agents/AgentDrawerPanel.test.tsx +2 -205
- package/src/components/agents/AgentDrawerPanel.tsx +23 -64
- package/src/components/agents/AgentMessageBubble.tsx +59 -33
- package/src/components/agents/AgentMessageList.tsx +4 -9
- package/src/components/agents/AgentQuestionPromptList.tsx +34 -212
- package/src/components/agents/AgentToolApprovalActions.tsx +13 -11
- package/src/components/agents/AgentToolPendingList.tsx +6 -6
- package/src/components/agents/AgentToolPendingReview.tsx +6 -6
- package/src/components/agents/QuestionRequestList.tsx +278 -0
- package/src/components/agents/agentPanelStyles.ts +9 -33
- package/src/components/agents/agentTypes.ts +25 -43
- package/src/components/agents/index.ts +4 -0
- package/src/components/assets/asset-library/sidebar/AssetLibraryList.tsx +10 -4
- package/src/components/chrome/WorkspaceTabStrip.tsx +4 -3
- package/src/components/common/chat/ChatBox.tsx +9 -9
- package/src/components/common/chat/ChatModelMenu.tsx +34 -4
- package/src/components/common/chat/ChatThread.tsx +23 -7
- package/src/components/common/connections/ProviderConnection.test.tsx +208 -0
- package/src/components/common/connections/ProviderConnection.tsx +277 -0
- package/src/components/common/connections/index.ts +5 -0
- package/src/components/common/containers/AppFrame.tsx +26 -19
- package/src/components/common/containers/AppHeader.tsx +20 -20
- package/src/components/common/documents/DocumentSwitcher.tsx +63 -58
- package/src/components/common/dropdown/MenuDropdownItem.tsx +14 -2
- package/src/components/common/dropdown/MenuPortal.tsx +3 -1
- package/src/components/common/dropdown/menuViewportPosition.test.ts +20 -10
- package/src/components/common/dropdown/menuViewportPosition.ts +35 -27
- package/src/components/common/overlays/Modal.tsx +13 -2
- package/src/components/editor/EditorLinkPromptDialog.tsx +1 -1
- package/src/ics/generateIcs.test.ts +153 -0
- package/src/ics/generateIcs.ts +197 -0
- package/src/index.ts +3 -0
- package/src/net/httpRetry.test.ts +117 -0
- package/src/net/httpRetry.ts +111 -0
- package/src/theme/appAccents.ts +6 -0
- package/src/theme/appIdentityCss.mjs +11 -0
- package/src/theme/appIdentityCss.ts +11 -0
- package/src/theme/themes/dark.ts +3 -1
- package/src/theme/themes/light.ts +3 -1
- package/src/bridge/googleProviderConfig.ts +0 -73
package/src/bridge/googleAuth.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Google OAuth scope sets apps request through the shell-owned credential
|
|
3
|
+
* flow (`credentials.connectOAuth`). The OAuth client machinery itself —
|
|
4
|
+
* PKCE, authorize URL, token exchange, refresh — lives in the Electron main
|
|
5
|
+
* process (`packages/shell/src/modules/credentials/googleOAuth.ts`); no
|
|
6
|
+
* renderer ever handles credential material.
|
|
7
|
+
*/
|
|
7
8
|
|
|
8
9
|
export const GMAIL_SCOPES = [
|
|
9
10
|
'https://www.googleapis.com/auth/gmail.modify',
|
|
@@ -13,149 +14,3 @@ export const GMAIL_SCOPES = [
|
|
|
13
14
|
export const CALENDAR_SCOPES = [
|
|
14
15
|
'https://www.googleapis.com/auth/calendar.events',
|
|
15
16
|
]
|
|
16
|
-
|
|
17
|
-
export const GOOGLE_AUTHORIZATION_ENDPOINT =
|
|
18
|
-
'https://accounts.google.com/o/oauth2/v2/auth'
|
|
19
|
-
export const GOOGLE_TOKEN_ENDPOINT = 'https://oauth2.googleapis.com/token'
|
|
20
|
-
|
|
21
|
-
const EXPIRY_SLACK_MS = 60_000
|
|
22
|
-
|
|
23
|
-
export interface PkcePair {
|
|
24
|
-
verifier: string
|
|
25
|
-
challenge: string
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function base64UrlEncode(bytes: Uint8Array): string {
|
|
29
|
-
let binary = ''
|
|
30
|
-
for (const byte of bytes) {
|
|
31
|
-
binary += String.fromCharCode(byte)
|
|
32
|
-
}
|
|
33
|
-
return btoa(binary).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '')
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export function randomUrlSafeString(byteLength = 32): string {
|
|
37
|
-
const bytes = new Uint8Array(byteLength)
|
|
38
|
-
crypto.getRandomValues(bytes)
|
|
39
|
-
return base64UrlEncode(bytes)
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export async function createPkcePair(): Promise<PkcePair> {
|
|
43
|
-
const verifier = randomUrlSafeString(48)
|
|
44
|
-
const digest = await crypto.subtle.digest(
|
|
45
|
-
'SHA-256',
|
|
46
|
-
new TextEncoder().encode(verifier),
|
|
47
|
-
)
|
|
48
|
-
return { verifier, challenge: base64UrlEncode(new Uint8Array(digest)) }
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export interface GoogleAuthUrlInput {
|
|
52
|
-
clientId: string
|
|
53
|
-
challenge: string
|
|
54
|
-
state: string
|
|
55
|
-
scopes?: string[]
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export function buildGoogleAuthorizationUrl(input: GoogleAuthUrlInput): string {
|
|
59
|
-
const url = new URL(GOOGLE_AUTHORIZATION_ENDPOINT)
|
|
60
|
-
url.searchParams.set('client_id', input.clientId)
|
|
61
|
-
url.searchParams.set('response_type', 'code')
|
|
62
|
-
url.searchParams.set('scope', (input.scopes ?? CALENDAR_SCOPES).join(' '))
|
|
63
|
-
url.searchParams.set('code_challenge', input.challenge)
|
|
64
|
-
url.searchParams.set('code_challenge_method', 'S256')
|
|
65
|
-
url.searchParams.set('state', input.state)
|
|
66
|
-
url.searchParams.set('access_type', 'offline')
|
|
67
|
-
url.searchParams.set('prompt', 'consent')
|
|
68
|
-
return url.toString()
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
interface GoogleTokenResponse {
|
|
72
|
-
access_token: string
|
|
73
|
-
refresh_token?: string
|
|
74
|
-
expires_in: number
|
|
75
|
-
scope: string
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
export function tokensFromTokenResponse(
|
|
79
|
-
payload: GoogleTokenResponse,
|
|
80
|
-
previousRefreshToken?: string,
|
|
81
|
-
now = Date.now(),
|
|
82
|
-
): GoogleOauthTokens {
|
|
83
|
-
const refreshToken = payload.refresh_token ?? previousRefreshToken
|
|
84
|
-
return {
|
|
85
|
-
accessToken: payload.access_token,
|
|
86
|
-
...(refreshToken === undefined ? {} : { refreshToken }),
|
|
87
|
-
expiresAt: now + payload.expires_in * 1000,
|
|
88
|
-
scope: payload.scope,
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
export function accessTokenIsFresh(
|
|
93
|
-
tokens: GoogleOauthTokens,
|
|
94
|
-
now = Date.now(),
|
|
95
|
-
): boolean {
|
|
96
|
-
return tokens.expiresAt - EXPIRY_SLACK_MS > now
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
export interface TokenExchangeInput {
|
|
100
|
-
clientId: string
|
|
101
|
-
clientSecret?: string
|
|
102
|
-
code: string
|
|
103
|
-
verifier: string
|
|
104
|
-
redirectUri: string
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
type FormPost = (
|
|
108
|
-
url: string,
|
|
109
|
-
body: string,
|
|
110
|
-
) => Promise<{ ok: boolean; status: number; body: string }>
|
|
111
|
-
|
|
112
|
-
export async function exchangeCodeForTokens(
|
|
113
|
-
post: FormPost,
|
|
114
|
-
input: TokenExchangeInput,
|
|
115
|
-
): Promise<GoogleOauthTokens> {
|
|
116
|
-
const body = new URLSearchParams({
|
|
117
|
-
client_id: input.clientId,
|
|
118
|
-
grant_type: 'authorization_code',
|
|
119
|
-
code: input.code,
|
|
120
|
-
code_verifier: input.verifier,
|
|
121
|
-
redirect_uri: input.redirectUri,
|
|
122
|
-
})
|
|
123
|
-
if (input.clientSecret) body.set('client_secret', input.clientSecret)
|
|
124
|
-
const response = await post(GOOGLE_TOKEN_ENDPOINT, body.toString())
|
|
125
|
-
if (!response.ok) {
|
|
126
|
-
throw new Error(
|
|
127
|
-
`Google token exchange failed (${response.status}): ${response.body}`,
|
|
128
|
-
)
|
|
129
|
-
}
|
|
130
|
-
return tokensFromTokenResponse(
|
|
131
|
-
JSON.parse(response.body) as GoogleTokenResponse,
|
|
132
|
-
)
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
export interface TokenRefreshInput {
|
|
136
|
-
clientId: string
|
|
137
|
-
clientSecret?: string
|
|
138
|
-
refreshToken: string
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
export async function refreshAccessToken(
|
|
142
|
-
post: FormPost,
|
|
143
|
-
input: TokenRefreshInput,
|
|
144
|
-
): Promise<GoogleOauthTokens> {
|
|
145
|
-
const body = new URLSearchParams({
|
|
146
|
-
client_id: input.clientId,
|
|
147
|
-
grant_type: 'refresh_token',
|
|
148
|
-
refresh_token: input.refreshToken,
|
|
149
|
-
})
|
|
150
|
-
if (input.clientSecret) body.set('client_secret', input.clientSecret)
|
|
151
|
-
const response = await post(GOOGLE_TOKEN_ENDPOINT, body.toString())
|
|
152
|
-
if (!response.ok) {
|
|
153
|
-
throw new Error(
|
|
154
|
-
`Google token refresh failed (${response.status}): ${response.body}`,
|
|
155
|
-
)
|
|
156
|
-
}
|
|
157
|
-
return tokensFromTokenResponse(
|
|
158
|
-
JSON.parse(response.body) as GoogleTokenResponse,
|
|
159
|
-
input.refreshToken,
|
|
160
|
-
)
|
|
161
|
-
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
export type McpClientTransportType = 'http' | 'sse' | 'stdio'
|
|
2
|
+
export type McpClientAuthType = 'none' | 'bearer' | 'apiKey'
|
|
3
|
+
|
|
4
|
+
/** A configured external MCP server (no secret material — that stays shell-side). */
|
|
5
|
+
export interface McpClientServerConfig {
|
|
6
|
+
/** Stable slug — `[a-z0-9-]+`. */
|
|
7
|
+
id: string
|
|
8
|
+
label: string
|
|
9
|
+
transport: McpClientTransportType
|
|
10
|
+
/** Remote endpoint for `http` / `sse` transports. */
|
|
11
|
+
url?: string
|
|
12
|
+
/** Executable for the `stdio` transport. */
|
|
13
|
+
command?: string
|
|
14
|
+
args?: string[]
|
|
15
|
+
enabled: boolean
|
|
16
|
+
authType: McpClientAuthType
|
|
17
|
+
/** Env var the stored secret is injected as, when `authType` is `apiKey`. */
|
|
18
|
+
apiKeyEnvVar?: string
|
|
19
|
+
/** Short tool names opted into auto-run; everything else asks first. */
|
|
20
|
+
autoApproveTools?: string[]
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface McpClientServerView extends McpClientServerConfig {
|
|
24
|
+
hasSecret: boolean
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface McpClientSettingsView {
|
|
28
|
+
enabled: boolean
|
|
29
|
+
servers: McpClientServerView[]
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface McpClientTestResult {
|
|
33
|
+
ok: boolean
|
|
34
|
+
serverId: string
|
|
35
|
+
toolNames: string[]
|
|
36
|
+
error?: string
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface McpToolInfo {
|
|
40
|
+
name: string
|
|
41
|
+
description?: string
|
|
42
|
+
inputSchema: Record<string, unknown>
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface McpToolRunResult {
|
|
46
|
+
content: string
|
|
47
|
+
isError: boolean
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function listMcpClientSettings(): Promise<McpClientSettingsView>
|
|
51
|
+
export function setMcpClientEnabled(
|
|
52
|
+
enabled: boolean,
|
|
53
|
+
): Promise<McpClientSettingsView>
|
|
54
|
+
export function upsertMcpClientServer(
|
|
55
|
+
config: McpClientServerConfig,
|
|
56
|
+
): Promise<McpClientSettingsView>
|
|
57
|
+
export function removeMcpClientServer(
|
|
58
|
+
serverId: string,
|
|
59
|
+
): Promise<McpClientSettingsView>
|
|
60
|
+
export function setMcpClientServerSecret(
|
|
61
|
+
serverId: string,
|
|
62
|
+
value: string | null,
|
|
63
|
+
): Promise<McpClientSettingsView>
|
|
64
|
+
export function testMcpClientServer(
|
|
65
|
+
config: McpClientServerConfig,
|
|
66
|
+
): Promise<McpClientTestResult>
|
|
67
|
+
export function listMcpClientServerTools(
|
|
68
|
+
serverId: string,
|
|
69
|
+
): Promise<McpToolInfo[]>
|
|
70
|
+
export function callMcpClientServerTool(
|
|
71
|
+
serverId: string,
|
|
72
|
+
toolName: string,
|
|
73
|
+
args: Record<string, unknown>,
|
|
74
|
+
): Promise<McpToolRunResult>
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { bridge } from './client.mjs'
|
|
2
|
+
import { PLATFORM_BRIDGE_METHODS } from './methods.mjs'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Outbound MCP client registry (external MCP servers whose tools in-app
|
|
6
|
+
* agents can call). Secret material never crosses this bridge — only the
|
|
7
|
+
* redacted settings view does. The shell gates these methods to the
|
|
8
|
+
* assistant app: this is machine-level, secret-bearing configuration.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @returns {Promise<import('./mcpClient.d.mts').McpClientSettingsView>}
|
|
13
|
+
*/
|
|
14
|
+
export async function listMcpClientSettings() {
|
|
15
|
+
return bridge.call(PLATFORM_BRIDGE_METHODS.MCPCLIENT_LIST, [])
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @param {boolean} enabled
|
|
20
|
+
* @returns {Promise<import('./mcpClient.d.mts').McpClientSettingsView>}
|
|
21
|
+
*/
|
|
22
|
+
export async function setMcpClientEnabled(enabled) {
|
|
23
|
+
return bridge.call(PLATFORM_BRIDGE_METHODS.MCPCLIENT_SET_ENABLED, [enabled])
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @param {import('./mcpClient.d.mts').McpClientServerConfig} config
|
|
28
|
+
* @returns {Promise<import('./mcpClient.d.mts').McpClientSettingsView>}
|
|
29
|
+
*/
|
|
30
|
+
export async function upsertMcpClientServer(config) {
|
|
31
|
+
return bridge.call(PLATFORM_BRIDGE_METHODS.MCPCLIENT_UPSERT, [config])
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* @param {string} serverId
|
|
36
|
+
* @returns {Promise<import('./mcpClient.d.mts').McpClientSettingsView>}
|
|
37
|
+
*/
|
|
38
|
+
export async function removeMcpClientServer(serverId) {
|
|
39
|
+
return bridge.call(PLATFORM_BRIDGE_METHODS.MCPCLIENT_REMOVE, [serverId])
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* @param {string} serverId
|
|
44
|
+
* @param {string | null} value
|
|
45
|
+
* @returns {Promise<import('./mcpClient.d.mts').McpClientSettingsView>}
|
|
46
|
+
*/
|
|
47
|
+
export async function setMcpClientServerSecret(serverId, value) {
|
|
48
|
+
return bridge.call(PLATFORM_BRIDGE_METHODS.MCPCLIENT_SET_SECRET, [
|
|
49
|
+
{ serverId, value },
|
|
50
|
+
])
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* @param {import('./mcpClient.d.mts').McpClientServerConfig} config
|
|
55
|
+
* @returns {Promise<import('./mcpClient.d.mts').McpClientTestResult>}
|
|
56
|
+
*/
|
|
57
|
+
export async function testMcpClientServer(config) {
|
|
58
|
+
return bridge.call(PLATFORM_BRIDGE_METHODS.MCPCLIENT_TEST, [config])
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* @param {string} serverId
|
|
63
|
+
* @returns {Promise<import('./mcpClient.d.mts').McpToolInfo[]>}
|
|
64
|
+
*/
|
|
65
|
+
export async function listMcpClientServerTools(serverId) {
|
|
66
|
+
return bridge.call(PLATFORM_BRIDGE_METHODS.MCPCLIENT_LIST_TOOLS, [serverId])
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* @param {string} serverId
|
|
71
|
+
* @param {string} toolName
|
|
72
|
+
* @param {Record<string, unknown>} args
|
|
73
|
+
* @returns {Promise<import('./mcpClient.d.mts').McpToolRunResult>}
|
|
74
|
+
*/
|
|
75
|
+
export async function callMcpClientServerTool(serverId, toolName, args) {
|
|
76
|
+
return bridge.call(PLATFORM_BRIDGE_METHODS.MCPCLIENT_CALL_TOOL, [
|
|
77
|
+
{ serverId, toolName, arguments: args },
|
|
78
|
+
])
|
|
79
|
+
}
|
package/src/bridge/methods.d.mts
CHANGED
|
@@ -20,6 +20,8 @@ export declare const PLATFORM_BRIDGE_METHODS: {
|
|
|
20
20
|
readonly SETTINGS_APP_UPDATE: 'settings.app.update'
|
|
21
21
|
readonly STORAGE_READ_JSON: 'storage.readJson'
|
|
22
22
|
readonly STORAGE_WRITE_JSON: 'storage.writeJson'
|
|
23
|
+
readonly OPERATIONS_RECORD: 'operations.record'
|
|
24
|
+
readonly OPERATIONS_LIST: 'operations.list'
|
|
23
25
|
readonly FS_LIST: 'fs.list'
|
|
24
26
|
readonly FS_READ_PREVIEW: 'fs.readPreview'
|
|
25
27
|
readonly FS_READ_BINARY: 'fs.readBinary'
|
|
@@ -63,6 +65,9 @@ export declare const PLATFORM_BRIDGE_METHODS: {
|
|
|
63
65
|
readonly NETWORK_FETCH: 'network.fetch'
|
|
64
66
|
readonly RESEARCH_QUERY: 'research.query'
|
|
65
67
|
readonly VISION_GENERATE_IMAGE: 'vision.generateImage'
|
|
68
|
+
readonly NOTIFICATIONS_SCHEDULE: 'notifications.schedule'
|
|
69
|
+
readonly NOTIFICATIONS_CANCEL: 'notifications.cancel'
|
|
70
|
+
readonly NOTIFICATIONS_CANCEL_ALL: 'notifications.cancelAll'
|
|
66
71
|
readonly OAUTH_LOOPBACK_AUTHORIZE: 'oauth.loopbackAuthorize'
|
|
67
72
|
readonly SECRETS_GET: 'secrets.get'
|
|
68
73
|
readonly SECRETS_SET: 'secrets.set'
|
|
@@ -74,6 +79,14 @@ export declare const PLATFORM_BRIDGE_METHODS: {
|
|
|
74
79
|
readonly MYTOOLS_DELETE: 'mytools.delete'
|
|
75
80
|
readonly MYTOOLS_TEST: 'mytools.test'
|
|
76
81
|
readonly MYTOOLS_RESEARCH: 'mytools.research'
|
|
82
|
+
readonly MCPCLIENT_LIST: 'mcpClient.list'
|
|
83
|
+
readonly MCPCLIENT_SET_ENABLED: 'mcpClient.setEnabled'
|
|
84
|
+
readonly MCPCLIENT_UPSERT: 'mcpClient.upsertServer'
|
|
85
|
+
readonly MCPCLIENT_REMOVE: 'mcpClient.removeServer'
|
|
86
|
+
readonly MCPCLIENT_SET_SECRET: 'mcpClient.setSecret'
|
|
87
|
+
readonly MCPCLIENT_TEST: 'mcpClient.testServer'
|
|
88
|
+
readonly MCPCLIENT_LIST_TOOLS: 'mcpClient.listTools'
|
|
89
|
+
readonly MCPCLIENT_CALL_TOOL: 'mcpClient.callTool'
|
|
77
90
|
readonly DEBUG_EMIT_EVENT: 'debug.emitEvent'
|
|
78
91
|
readonly PLUGINS_INSTALL: 'plugins.install'
|
|
79
92
|
readonly PLUGINS_EVALUATE_BUILD: 'plugins.evaluateBuild'
|
|
@@ -88,6 +101,11 @@ export declare const PLATFORM_BRIDGE_METHODS: {
|
|
|
88
101
|
readonly CREDENTIALS_LIST: 'credentials.list'
|
|
89
102
|
readonly CREDENTIALS_SET: 'credentials.set'
|
|
90
103
|
readonly CREDENTIALS_DELETE: 'credentials.delete'
|
|
104
|
+
readonly CREDENTIALS_STATUS: 'credentials.status'
|
|
105
|
+
readonly CREDENTIALS_SET_OAUTH_CONFIG: 'credentials.setOAuthConfig'
|
|
106
|
+
readonly CREDENTIALS_CONNECT_OAUTH: 'credentials.connectOAuth'
|
|
107
|
+
readonly CREDENTIALS_DISCONNECT: 'credentials.disconnect'
|
|
108
|
+
readonly CREDENTIALS_OAUTH_ACCESS_TOKEN: 'credentials.oauthAccessToken'
|
|
91
109
|
readonly AGENTS_SESSIONS_CREATE: 'agents.sessions.create'
|
|
92
110
|
readonly AGENTS_SESSIONS_LIST: 'agents.sessions.list'
|
|
93
111
|
readonly AGENTS_SESSIONS_READ: 'agents.sessions.read'
|
|
@@ -98,6 +116,7 @@ export declare const PLATFORM_BRIDGE_METHODS: {
|
|
|
98
116
|
readonly AGENTS_JOBS_READ: 'agents.jobs.read'
|
|
99
117
|
readonly AGENTS_JOBS_CANCEL: 'agents.jobs.cancel'
|
|
100
118
|
readonly AGENTS_JOBS_RUN_NOW: 'agents.jobs.runNow'
|
|
119
|
+
readonly AGENTS_DELEGATIONS_LIST: 'agents.delegations.list'
|
|
101
120
|
readonly AGENTS_SESSION_SEND_MESSAGE: 'agents.session.sendMessage'
|
|
102
121
|
readonly AGENTS_SESSION_APPROVE_TOOLS: 'agents.session.approveTools'
|
|
103
122
|
readonly AGENTS_SESSION_REJECT_TOOLS: 'agents.session.rejectTools'
|
|
@@ -141,13 +160,16 @@ export declare const PLATFORM_BRIDGE_METHOD_PREFIXES: {
|
|
|
141
160
|
readonly NETWORK: 'network.'
|
|
142
161
|
readonly RESEARCH: 'research.'
|
|
143
162
|
readonly VISION: 'vision.'
|
|
163
|
+
readonly NOTIFICATIONS: 'notifications.'
|
|
144
164
|
readonly OAUTH: 'oauth.'
|
|
145
165
|
readonly SECRETS: 'secrets.'
|
|
146
166
|
readonly MYTOOLS: 'mytools.'
|
|
167
|
+
readonly MCPCLIENT: 'mcpClient.'
|
|
147
168
|
readonly AGENTS: 'agents.'
|
|
148
169
|
readonly PEOPLE: 'people.'
|
|
149
170
|
readonly DEBUG: 'debug.'
|
|
150
171
|
readonly DOCUMENTS: 'documents.'
|
|
172
|
+
readonly OPERATIONS: 'operations.'
|
|
151
173
|
}
|
|
152
174
|
|
|
153
175
|
export type PlatformBridgeMethodName =
|
package/src/bridge/methods.mjs
CHANGED
|
@@ -27,6 +27,8 @@ export const PLATFORM_BRIDGE_METHODS = {
|
|
|
27
27
|
|
|
28
28
|
STORAGE_READ_JSON: 'storage.readJson',
|
|
29
29
|
STORAGE_WRITE_JSON: 'storage.writeJson',
|
|
30
|
+
OPERATIONS_RECORD: 'operations.record',
|
|
31
|
+
OPERATIONS_LIST: 'operations.list',
|
|
30
32
|
|
|
31
33
|
FS_LIST: 'fs.list',
|
|
32
34
|
FS_READ_PREVIEW: 'fs.readPreview',
|
|
@@ -78,6 +80,9 @@ export const PLATFORM_BRIDGE_METHODS = {
|
|
|
78
80
|
NETWORK_FETCH: 'network.fetch',
|
|
79
81
|
RESEARCH_QUERY: 'research.query',
|
|
80
82
|
VISION_GENERATE_IMAGE: 'vision.generateImage',
|
|
83
|
+
NOTIFICATIONS_SCHEDULE: 'notifications.schedule',
|
|
84
|
+
NOTIFICATIONS_CANCEL: 'notifications.cancel',
|
|
85
|
+
NOTIFICATIONS_CANCEL_ALL: 'notifications.cancelAll',
|
|
81
86
|
OAUTH_LOOPBACK_AUTHORIZE: 'oauth.loopbackAuthorize',
|
|
82
87
|
SECRETS_GET: 'secrets.get',
|
|
83
88
|
SECRETS_SET: 'secrets.set',
|
|
@@ -90,6 +95,15 @@ export const PLATFORM_BRIDGE_METHODS = {
|
|
|
90
95
|
MYTOOLS_TEST: 'mytools.test',
|
|
91
96
|
MYTOOLS_RESEARCH: 'mytools.research',
|
|
92
97
|
|
|
98
|
+
MCPCLIENT_LIST: 'mcpClient.list',
|
|
99
|
+
MCPCLIENT_SET_ENABLED: 'mcpClient.setEnabled',
|
|
100
|
+
MCPCLIENT_UPSERT: 'mcpClient.upsertServer',
|
|
101
|
+
MCPCLIENT_REMOVE: 'mcpClient.removeServer',
|
|
102
|
+
MCPCLIENT_SET_SECRET: 'mcpClient.setSecret',
|
|
103
|
+
MCPCLIENT_TEST: 'mcpClient.testServer',
|
|
104
|
+
MCPCLIENT_LIST_TOOLS: 'mcpClient.listTools',
|
|
105
|
+
MCPCLIENT_CALL_TOOL: 'mcpClient.callTool',
|
|
106
|
+
|
|
93
107
|
DEBUG_EMIT_EVENT: 'debug.emitEvent',
|
|
94
108
|
|
|
95
109
|
PLUGINS_INSTALL: 'plugins.install',
|
|
@@ -106,6 +120,11 @@ export const PLATFORM_BRIDGE_METHODS = {
|
|
|
106
120
|
CREDENTIALS_LIST: 'credentials.list',
|
|
107
121
|
CREDENTIALS_SET: 'credentials.set',
|
|
108
122
|
CREDENTIALS_DELETE: 'credentials.delete',
|
|
123
|
+
CREDENTIALS_STATUS: 'credentials.status',
|
|
124
|
+
CREDENTIALS_SET_OAUTH_CONFIG: 'credentials.setOAuthConfig',
|
|
125
|
+
CREDENTIALS_CONNECT_OAUTH: 'credentials.connectOAuth',
|
|
126
|
+
CREDENTIALS_DISCONNECT: 'credentials.disconnect',
|
|
127
|
+
CREDENTIALS_OAUTH_ACCESS_TOKEN: 'credentials.oauthAccessToken',
|
|
109
128
|
|
|
110
129
|
AGENTS_SESSIONS_CREATE: 'agents.sessions.create',
|
|
111
130
|
AGENTS_SESSIONS_LIST: 'agents.sessions.list',
|
|
@@ -118,6 +137,7 @@ export const PLATFORM_BRIDGE_METHODS = {
|
|
|
118
137
|
AGENTS_JOBS_READ: 'agents.jobs.read',
|
|
119
138
|
AGENTS_JOBS_CANCEL: 'agents.jobs.cancel',
|
|
120
139
|
AGENTS_JOBS_RUN_NOW: 'agents.jobs.runNow',
|
|
140
|
+
AGENTS_DELEGATIONS_LIST: 'agents.delegations.list',
|
|
121
141
|
|
|
122
142
|
AGENTS_SESSION_SEND_MESSAGE: 'agents.session.sendMessage',
|
|
123
143
|
AGENTS_SESSION_APPROVE_TOOLS: 'agents.session.approveTools',
|
|
@@ -163,11 +183,14 @@ export const PLATFORM_BRIDGE_METHOD_PREFIXES = {
|
|
|
163
183
|
NETWORK: 'network.',
|
|
164
184
|
RESEARCH: 'research.',
|
|
165
185
|
VISION: 'vision.',
|
|
186
|
+
NOTIFICATIONS: 'notifications.',
|
|
166
187
|
OAUTH: 'oauth.',
|
|
167
188
|
SECRETS: 'secrets.',
|
|
168
189
|
MYTOOLS: 'mytools.',
|
|
190
|
+
MCPCLIENT: 'mcpClient.',
|
|
169
191
|
AGENTS: 'agents.',
|
|
170
192
|
PEOPLE: 'people.',
|
|
171
193
|
DEBUG: 'debug.',
|
|
172
194
|
DOCUMENTS: 'documents.',
|
|
195
|
+
OPERATIONS: 'operations.',
|
|
173
196
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export function scheduleNotification(request: {
|
|
2
|
+
id: string
|
|
3
|
+
title: string
|
|
4
|
+
body?: string
|
|
5
|
+
fireAt: number
|
|
6
|
+
}): Promise<{ ok: true }>
|
|
7
|
+
|
|
8
|
+
export function cancelNotification(request: {
|
|
9
|
+
id: string
|
|
10
|
+
}): Promise<{ ok: true }>
|
|
11
|
+
|
|
12
|
+
export function cancelAllNotifications(): Promise<{ ok: true }>
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { bridge } from './client.mjs'
|
|
2
|
+
import { PLATFORM_BRIDGE_METHODS } from './methods.mjs'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* OS notification scheduling — a benign local capability (no plugin permission).
|
|
6
|
+
* Schedules are held in the shell main process and die when the app quits;
|
|
7
|
+
* reminders while the app is closed are out of scope. The app-side scheduler
|
|
8
|
+
* that decides *what* to schedule is the caller's responsibility.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Schedule an OS notification to fire at `fireAt`. Reusing an `id` replaces any
|
|
13
|
+
* pending schedule (dedupe). `fireAt` in the past fires ~immediately.
|
|
14
|
+
* @param {{ id: string, title: string, body?: string, fireAt: number }} request
|
|
15
|
+
* @returns {Promise<{ ok: true }>}
|
|
16
|
+
*/
|
|
17
|
+
export async function scheduleNotification(request) {
|
|
18
|
+
return bridge.call(PLATFORM_BRIDGE_METHODS.NOTIFICATIONS_SCHEDULE, [request])
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Cancel a pending schedule; no-op if the id is unknown.
|
|
23
|
+
* @param {{ id: string }} request
|
|
24
|
+
* @returns {Promise<{ ok: true }>}
|
|
25
|
+
*/
|
|
26
|
+
export async function cancelNotification(request) {
|
|
27
|
+
return bridge.call(PLATFORM_BRIDGE_METHODS.NOTIFICATIONS_CANCEL, [request])
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Clear every pending schedule (used when an app reloads its reminder set).
|
|
32
|
+
* @returns {Promise<{ ok: true }>}
|
|
33
|
+
*/
|
|
34
|
+
export async function cancelAllNotifications() {
|
|
35
|
+
return bridge.call(PLATFORM_BRIDGE_METHODS.NOTIFICATIONS_CANCEL_ALL, [])
|
|
36
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export type PlatformOperationLane = 'user' | 'agent'
|
|
2
|
+
|
|
3
|
+
export interface PlatformOperationInput {
|
|
4
|
+
/** Who acted: a person in the UI, or an agent doing work. */
|
|
5
|
+
lane: PlatformOperationLane
|
|
6
|
+
/** Stable machine-readable kind, dot-namespaced, e.g. `document.save`. */
|
|
7
|
+
kind: string
|
|
8
|
+
/** Originating app slug, or `shell` for platform-level operations. */
|
|
9
|
+
appSlug: string
|
|
10
|
+
/** One human-readable sentence describing the operation. */
|
|
11
|
+
summary: string
|
|
12
|
+
/** Optional free-form elaboration. */
|
|
13
|
+
detail?: string
|
|
14
|
+
/** Optional references (path, sessionId, runId, …) for drill-down. */
|
|
15
|
+
refs?: Record<string, string>
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface PlatformOperation extends PlatformOperationInput {
|
|
19
|
+
id: string
|
|
20
|
+
/** ISO timestamp stamped by the shell. */
|
|
21
|
+
at: string
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface PlatformOperationsListQuery {
|
|
25
|
+
lane?: PlatformOperationLane
|
|
26
|
+
/** Max entries returned, newest first. */
|
|
27
|
+
limit?: number
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface PlatformOperationsListResult {
|
|
31
|
+
operations: PlatformOperation[]
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export declare function recordPlatformOperation(
|
|
35
|
+
input: PlatformOperationInput,
|
|
36
|
+
): Promise<PlatformOperation>
|
|
37
|
+
|
|
38
|
+
export declare function listPlatformOperations(
|
|
39
|
+
query?: PlatformOperationsListQuery,
|
|
40
|
+
): Promise<PlatformOperationsListResult>
|
|
41
|
+
|
|
42
|
+
export declare function onPlatformOperationRecorded(
|
|
43
|
+
listener: (operation: PlatformOperation) => void,
|
|
44
|
+
): () => void
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { bridge } from './client.mjs'
|
|
2
|
+
import { PLATFORM_BRIDGE_EVENTS } from './events.mjs'
|
|
3
|
+
import { PLATFORM_BRIDGE_METHODS } from './methods.mjs'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Platform operations ledger: one suite-wide record of what happened,
|
|
7
|
+
* split into two lanes — `user` interactions and `agent` interactions.
|
|
8
|
+
* The shell stores every entry and broadcasts each append, so UIs (the
|
|
9
|
+
* PureAssistant tab in particular) can follow activity in real time.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Record one operation into the ledger.
|
|
14
|
+
*
|
|
15
|
+
* @param {import('./operations.d.mts').PlatformOperationInput} input
|
|
16
|
+
* @returns {Promise<import('./operations.d.mts').PlatformOperation>}
|
|
17
|
+
*/
|
|
18
|
+
export async function recordPlatformOperation(input) {
|
|
19
|
+
return bridge.call(PLATFORM_BRIDGE_METHODS.OPERATIONS_RECORD, [input])
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* List recorded operations, newest first.
|
|
24
|
+
*
|
|
25
|
+
* @param {import('./operations.d.mts').PlatformOperationsListQuery} [query]
|
|
26
|
+
* @returns {Promise<import('./operations.d.mts').PlatformOperationsListResult>}
|
|
27
|
+
*/
|
|
28
|
+
export async function listPlatformOperations(query) {
|
|
29
|
+
return bridge.call(PLATFORM_BRIDGE_METHODS.OPERATIONS_LIST, [query ?? {}])
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Subscribe to ledger appends. The listener receives each new operation.
|
|
34
|
+
*
|
|
35
|
+
* @param {(operation: import('./operations.d.mts').PlatformOperation) => void} listener
|
|
36
|
+
* @returns {() => void} unsubscribe
|
|
37
|
+
*/
|
|
38
|
+
export function onPlatformOperationRecorded(listener) {
|
|
39
|
+
return bridge.onEvent(PLATFORM_BRIDGE_EVENTS.OPERATIONS_CHANGED, listener)
|
|
40
|
+
}
|
|
@@ -104,16 +104,35 @@ describe('usePlatformAgentSessionDrive', () => {
|
|
|
104
104
|
;(globalThis as ReactActGlobal).IS_REACT_ACT_ENVIRONMENT = true
|
|
105
105
|
})
|
|
106
106
|
|
|
107
|
-
beforeEach(() => {
|
|
108
|
-
latestDrive = null
|
|
109
|
-
host = document.createElement('div')
|
|
110
|
-
document.body.appendChild(host)
|
|
111
|
-
root = createRoot(host)
|
|
112
|
-
agentsMock.readAgentSession.mockResolvedValue(sessionRecord)
|
|
113
|
-
agentsMock.advanceAgentSessionPending.mockResolvedValue(
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
107
|
+
beforeEach(() => {
|
|
108
|
+
latestDrive = null
|
|
109
|
+
host = document.createElement('div')
|
|
110
|
+
document.body.appendChild(host)
|
|
111
|
+
root = createRoot(host)
|
|
112
|
+
agentsMock.readAgentSession.mockResolvedValue(sessionRecord)
|
|
113
|
+
agentsMock.advanceAgentSessionPending.mockResolvedValue({
|
|
114
|
+
session: sessionRecord,
|
|
115
|
+
snapshot: {
|
|
116
|
+
runId: sessionRecord.runId,
|
|
117
|
+
state: sessionRecord.snapshot.state,
|
|
118
|
+
turn: sessionRecord.snapshot.turn,
|
|
119
|
+
messages: sessionRecord.snapshot.messages,
|
|
120
|
+
},
|
|
121
|
+
effectiveToolPolicy: {
|
|
122
|
+
allowedTargetApps: ['book'],
|
|
123
|
+
defaultRequiresApproval: false,
|
|
124
|
+
approvalOverrides: {
|
|
125
|
+
'book.write': true,
|
|
126
|
+
'book.review': true,
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
manualPendingToolCalls: [
|
|
130
|
+
{ id: 'call-manual', name: 'book.review', arguments: {} },
|
|
131
|
+
],
|
|
132
|
+
})
|
|
133
|
+
agentsMock.readAgentLlmMenuOptions.mockResolvedValue([])
|
|
134
|
+
agentsMock.onAgentRunEvent.mockReturnValue(vi.fn())
|
|
135
|
+
})
|
|
117
136
|
|
|
118
137
|
afterEach(() => {
|
|
119
138
|
act(() => root.unmount())
|