@pixelbyte-software/pixcode 1.51.1 → 1.51.3
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/CODE_OF_CONDUCT.md +41 -41
- package/CONTRIBUTING.md +155 -155
- package/LICENSE +718 -718
- package/README.de.md +169 -169
- package/README.ja.md +167 -167
- package/README.ko.md +167 -167
- package/README.md +419 -419
- package/README.ru.md +169 -169
- package/README.tr.md +298 -298
- package/README.zh-CN.md +167 -167
- package/SECURITY.md +46 -46
- package/dist/api-automation.html +110 -110
- package/dist/api-docs.html +548 -548
- package/dist/assets/{index-DARIZgoD.js → index-17CwxHSZ.js} +185 -185
- package/dist/assets/index-B9N-gfOQ.css +32 -0
- package/dist/clear-cache.html +85 -85
- package/dist/convert-icons.md +52 -52
- package/dist/docs.html +308 -308
- package/dist/favicon.svg +8 -8
- package/dist/features.html +133 -133
- package/dist/generate-icons.js +48 -48
- package/dist/humans.txt +15 -15
- package/dist/icons/codex-white.svg +3 -3
- package/dist/icons/codex.svg +3 -3
- package/dist/icons/cursor-white.svg +11 -11
- package/dist/icons/icon-128x128.svg +9 -9
- package/dist/icons/icon-144x144.svg +9 -9
- package/dist/icons/icon-152x152.svg +9 -9
- package/dist/icons/icon-192x192.svg +9 -9
- package/dist/icons/icon-384x384.svg +9 -9
- package/dist/icons/icon-512x512.svg +9 -9
- package/dist/icons/icon-72x72.svg +9 -9
- package/dist/icons/icon-96x96.svg +9 -9
- package/dist/icons/icon-template.svg +9 -9
- package/dist/icons/qwen-logo.svg +14 -14
- package/dist/index.html +59 -59
- package/dist/landing.html +268 -268
- package/dist/llms-full.txt +119 -119
- package/dist/llms.txt +53 -53
- package/dist/logo.svg +12 -12
- package/dist/manifest.json +60 -60
- package/dist/openapi.yaml +1696 -1696
- package/dist/orchestration.html +125 -125
- package/dist/robots.txt +4 -4
- package/dist/site.css +692 -692
- package/dist/sitemap.xml +51 -51
- package/dist/sw.js +132 -132
- package/dist-server/server/cli.js +96 -96
- package/dist-server/server/daemon/manager.js +33 -33
- package/dist-server/server/daemon-manager.js +64 -64
- package/dist-server/server/index.js +125 -4
- package/dist-server/server/index.js.map +1 -1
- package/dist-server/server/modules/orchestration/a2a/adapters/json-event.adapter.js +84 -0
- package/dist-server/server/modules/orchestration/a2a/adapters/json-event.adapter.js.map +1 -0
- package/dist-server/server/modules/orchestration/a2a/adapters/json-event.adapter.test.js +43 -0
- package/dist-server/server/modules/orchestration/a2a/adapters/json-event.adapter.test.js.map +1 -0
- package/dist-server/server/modules/orchestration/hermes/hermes.routes.js +55 -1
- package/dist-server/server/modules/orchestration/hermes/hermes.routes.js.map +1 -1
- package/dist-server/server/modules/orchestration/index.js +1 -0
- package/dist-server/server/modules/orchestration/index.js.map +1 -1
- package/dist-server/server/routes/commands.js +25 -25
- package/dist-server/server/routes/git.js +17 -17
- package/dist-server/server/routes/live-view.js +46 -46
- package/dist-server/server/services/hermes-gateway.js +310 -0
- package/dist-server/server/services/hermes-gateway.js.map +1 -1
- package/dist-server/server/services/public-api-manifest.js +59 -51
- package/dist-server/server/services/public-api-manifest.js.map +1 -1
- package/package.json +222 -222
- package/scripts/fix-node-pty.js +67 -67
- package/scripts/github/create-v1.38-issues.mjs +351 -351
- package/scripts/github/create-vscode-workbench-issues.mjs +121 -121
- package/scripts/hermes/configure-pixcode-mcp.mjs +165 -163
- package/scripts/hermes/pixcode-mcp-server.mjs +1009 -958
- package/scripts/smoke/changes-panel-layout.mjs +48 -48
- package/scripts/smoke/chat-composer-fixed-layout.mjs +55 -55
- package/scripts/smoke/chat-message-timeline-order.mjs +41 -41
- package/scripts/smoke/chat-realtime-hydration.mjs +44 -44
- package/scripts/smoke/chat-session-provider-pools.mjs +35 -35
- package/scripts/smoke/chat-session-state.mjs +19 -19
- package/scripts/smoke/code-editor-theme.mjs +55 -55
- package/scripts/smoke/code-editor-vscode-engine.mjs +91 -91
- package/scripts/smoke/command-center-agent-writes.mjs +79 -79
- package/scripts/smoke/command-center-non-git.mjs +46 -46
- package/scripts/smoke/context-packet.mjs +43 -43
- package/scripts/smoke/control-room-ux-redesign.mjs +91 -91
- package/scripts/smoke/daemon-entrypoint.mjs +20 -20
- package/scripts/smoke/default-landing-routing.mjs +33 -33
- package/scripts/smoke/desktop-native-notifications.mjs +30 -30
- package/scripts/smoke/desktop-tray-icon.mjs +33 -33
- package/scripts/smoke/discord-release-workflow.mjs +24 -24
- package/scripts/smoke/git-install-update.mjs +255 -255
- package/scripts/smoke/handoff-artifact-protocol.mjs +50 -50
- package/scripts/smoke/hermes-api-install.mjs +56 -56
- package/scripts/smoke/hermes-gateway-persistence.mjs +104 -104
- package/scripts/smoke/hermes-mcp-pixcode-roundtrip.mjs +426 -367
- package/scripts/smoke/hermes-rest-chat-api.mjs +162 -162
- package/scripts/smoke/hermes-rest-chat-live.mjs +45 -45
- package/scripts/smoke/hermes-rest-codex-launch.mjs +209 -209
- package/scripts/smoke/hermes-rest-gateway.mjs +79 -70
- package/scripts/smoke/hermes-rest-live.mjs +42 -42
- package/scripts/smoke/hermes-roundtrip.mjs +167 -167
- package/scripts/smoke/hermes-settings-commands.mjs +349 -346
- package/scripts/smoke/hermes-smoke-launcher-guard.mjs +34 -34
- package/scripts/smoke/live-view-diagnostics.mjs +53 -53
- package/scripts/smoke/live-view-environment.mjs +92 -92
- package/scripts/smoke/live-view-integration.mjs +450 -450
- package/scripts/smoke/mac-desktop-runtime.mjs +37 -37
- package/scripts/smoke/mobile-tunnel-guidance.mjs +29 -29
- package/scripts/smoke/model-registry.mjs +36 -36
- package/scripts/smoke/multi-project-ui.mjs +45 -45
- package/scripts/smoke/multi-worker-slots.mjs +42 -42
- package/scripts/smoke/notification-center.mjs +87 -87
- package/scripts/smoke/notification-inapp-preference.mjs +23 -23
- package/scripts/smoke/notification-taxonomy.mjs +58 -58
- package/scripts/smoke/orchestration-api.mjs +172 -172
- package/scripts/smoke/orchestration-execution-dashboard.mjs +33 -33
- package/scripts/smoke/orchestration-live-run.mjs +176 -176
- package/scripts/smoke/orchestration-mobile-scroll.mjs +29 -29
- package/scripts/smoke/orchestration-model-sync.mjs +30 -30
- package/scripts/smoke/orchestration-permission-fallback.mjs +34 -34
- package/scripts/smoke/orchestration-runtime-guards.mjs +48 -48
- package/scripts/smoke/orchestration-user-facing-output.mjs +25 -25
- package/scripts/smoke/permission-policy.mjs +50 -50
- package/scripts/smoke/pixcode-workbench-1-48.mjs +167 -164
- package/scripts/smoke/provider-models-opencode-live.mjs +66 -66
- package/scripts/smoke/provider-rest-api.mjs +124 -124
- package/scripts/smoke/provider-selection-status.mjs +52 -52
- package/scripts/smoke/run-state-refresh.mjs +52 -52
- package/scripts/smoke/runtime-manager.mjs +99 -99
- package/scripts/smoke/shell-manual-disconnect.mjs +30 -30
- package/scripts/smoke/side-panel-editor-layout.mjs +34 -34
- package/scripts/smoke/static-root-routing.mjs +21 -21
- package/scripts/smoke/strict-handoff-compact.mjs +60 -60
- package/scripts/smoke/taskmaster-config.mjs +24 -24
- package/scripts/smoke/taskmaster-execution-telegram.mjs +3 -3
- package/scripts/smoke/taskmaster-onboarding.mjs +3 -3
- package/scripts/smoke/taskmaster-run-graph.mjs +3 -3
- package/scripts/smoke/telegram-control.mjs +242 -242
- package/scripts/smoke/tunnel-persistence.mjs +56 -56
- package/scripts/smoke/update-issue-progress.mjs +69 -69
- package/scripts/smoke/update-ux.mjs +55 -55
- package/scripts/smoke/v138-completion.mjs +132 -132
- package/scripts/smoke/v138-desktop-release-hardening.mjs +69 -69
- package/scripts/smoke/v138-diagnostics.mjs +63 -63
- package/scripts/smoke/v138-issue-planner.mjs +33 -33
- package/scripts/smoke/v143-remote-control.mjs +76 -76
- package/scripts/smoke/v144-production-loop.mjs +47 -47
- package/scripts/smoke/v145-platformization.mjs +46 -46
- package/scripts/smoke/v146-control-room-ui.mjs +150 -150
- package/scripts/smoke/version-modal-autoshow.mjs +29 -29
- package/scripts/smoke/vscode-workbench-layout.mjs +63 -63
- package/scripts/smoke/vscode-workbench-polish.mjs +461 -436
- package/scripts/smoke/workflow-fallback-replay.mjs +56 -56
- package/scripts/smoke/workflow-templates.mjs +43 -43
- package/scripts/smoke/workflow-trace-timeline.mjs +46 -46
- package/scripts/update-git-install.mjs +293 -293
- package/server/claude-sdk.js +920 -920
- package/server/cli.js +1039 -1039
- package/server/constants/config.js +4 -4
- package/server/cursor-cli.js +344 -344
- package/server/daemon/manager.js +563 -563
- package/server/daemon-manager.js +964 -964
- package/server/database/db.js +921 -921
- package/server/database/json-store.js +197 -197
- package/server/gemini-cli.js +550 -550
- package/server/gemini-response-handler.js +79 -79
- package/server/index.js +131 -3
- package/server/load-env.js +35 -35
- package/server/middleware/auth.js +175 -175
- package/server/modules/orchestration/a2a/adapter-registry.ts +108 -108
- package/server/modules/orchestration/a2a/adapters/abstract-a2a.adapter.ts +63 -63
- package/server/modules/orchestration/a2a/adapters/claude-code.adapter.ts +286 -286
- package/server/modules/orchestration/a2a/adapters/codex.adapter.ts +244 -244
- package/server/modules/orchestration/a2a/adapters/cursor.adapter.ts +249 -249
- package/server/modules/orchestration/a2a/adapters/gemini.adapter.ts +248 -248
- package/server/modules/orchestration/a2a/adapters/json-event.adapter.test.ts +60 -0
- package/server/modules/orchestration/a2a/adapters/json-event.adapter.ts +101 -0
- package/server/modules/orchestration/a2a/adapters/opencode.adapter.ts +248 -248
- package/server/modules/orchestration/a2a/adapters/qwen.adapter.ts +248 -248
- package/server/modules/orchestration/a2a/agent-card.ts +55 -55
- package/server/modules/orchestration/a2a/routes.ts +590 -590
- package/server/modules/orchestration/a2a/task-store.ts +178 -178
- package/server/modules/orchestration/a2a/types.ts +126 -126
- package/server/modules/orchestration/a2a/validator.ts +113 -113
- package/server/modules/orchestration/hermes/hermes.routes.ts +642 -583
- package/server/modules/orchestration/index.ts +101 -100
- package/server/modules/orchestration/preview/port-watcher.ts +112 -112
- package/server/modules/orchestration/preview/preview-proxy.ts +60 -60
- package/server/modules/orchestration/preview/types.ts +19 -19
- package/server/modules/orchestration/security/permission-policy.ts +401 -401
- package/server/modules/orchestration/tasks/orchestration-task-store.ts +41 -41
- package/server/modules/orchestration/tasks/orchestration-task.routes.ts +64 -64
- package/server/modules/orchestration/tasks/orchestration-task.service.ts +209 -209
- package/server/modules/orchestration/tasks/orchestration-task.types.ts +40 -40
- package/server/modules/orchestration/tasks/task-run-graph.ts +155 -155
- package/server/modules/orchestration/workflows/approval-queue.ts +106 -106
- package/server/modules/orchestration/workflows/built-in-workflows.ts +127 -127
- package/server/modules/orchestration/workflows/context-packet.ts +186 -186
- package/server/modules/orchestration/workflows/handoff-artifact.ts +175 -175
- package/server/modules/orchestration/workflows/workflow-fallback-policy.ts +161 -161
- package/server/modules/orchestration/workflows/workflow-replay.ts +254 -254
- package/server/modules/orchestration/workflows/workflow-runner.ts +2070 -2070
- package/server/modules/orchestration/workflows/workflow-store.ts +97 -97
- package/server/modules/orchestration/workflows/workflow-templates.ts +272 -272
- package/server/modules/orchestration/workflows/workflow-trace.ts +424 -424
- package/server/modules/orchestration/workflows/workflow.routes.ts +586 -586
- package/server/modules/orchestration/workflows/workflow.types.ts +111 -111
- package/server/modules/orchestration/workflows/workspace-target.ts +122 -122
- package/server/modules/orchestration/workspace/docker-workspace.ts +136 -136
- package/server/modules/orchestration/workspace/path-safety.ts +55 -55
- package/server/modules/orchestration/workspace/types.ts +52 -52
- package/server/modules/orchestration/workspace/workspace-manager.ts +102 -102
- package/server/modules/orchestration/workspace/worktree-workspace.ts +126 -126
- package/server/modules/providers/index.ts +2 -2
- package/server/modules/providers/list/claude/claude-auth.provider.ts +146 -146
- package/server/modules/providers/list/claude/claude-mcp.provider.ts +135 -135
- package/server/modules/providers/list/claude/claude-sessions.provider.ts +306 -306
- package/server/modules/providers/list/claude/claude.provider.ts +15 -15
- package/server/modules/providers/list/codex/codex-auth.provider.ts +117 -117
- package/server/modules/providers/list/codex/codex-mcp.provider.ts +135 -135
- package/server/modules/providers/list/codex/codex-sessions.provider.ts +319 -319
- package/server/modules/providers/list/codex/codex.provider.ts +15 -15
- package/server/modules/providers/list/cursor/cursor-auth.provider.ts +147 -147
- package/server/modules/providers/list/cursor/cursor-mcp.provider.ts +108 -108
- package/server/modules/providers/list/cursor/cursor-sessions.provider.ts +421 -421
- package/server/modules/providers/list/cursor/cursor.provider.ts +15 -15
- package/server/modules/providers/list/gemini/gemini-auth.provider.ts +173 -173
- package/server/modules/providers/list/gemini/gemini-mcp.provider.ts +110 -110
- package/server/modules/providers/list/gemini/gemini-sessions.provider.ts +227 -227
- package/server/modules/providers/list/gemini/gemini.provider.ts +15 -15
- package/server/modules/providers/list/opencode/opencode-auth.provider.ts +131 -131
- package/server/modules/providers/list/opencode/opencode-mcp.provider.ts +126 -126
- package/server/modules/providers/list/opencode/opencode-sessions.provider.ts +286 -286
- package/server/modules/providers/list/opencode/opencode.provider.ts +29 -29
- package/server/modules/providers/list/qwen/qwen-auth.provider.ts +146 -146
- package/server/modules/providers/list/qwen/qwen-mcp.provider.ts +114 -114
- package/server/modules/providers/list/qwen/qwen-sessions.provider.ts +265 -265
- package/server/modules/providers/list/qwen/qwen.provider.ts +21 -21
- package/server/modules/providers/provider.registry.ts +40 -40
- package/server/modules/providers/provider.routes.ts +944 -944
- package/server/modules/providers/services/mcp.service.ts +86 -86
- package/server/modules/providers/services/provider-auth.service.ts +26 -26
- package/server/modules/providers/services/sessions.service.ts +45 -45
- package/server/modules/providers/shared/base/abstract.provider.ts +20 -20
- package/server/modules/providers/shared/mcp/mcp.provider.ts +151 -151
- package/server/modules/providers/shared/provider-configs.ts +142 -142
- package/server/modules/providers/tests/mcp.test.ts +293 -293
- package/server/openai-codex.js +462 -462
- package/server/opencode-cli.js +491 -491
- package/server/opencode-response-handler.js +111 -111
- package/server/projects.js +3008 -3008
- package/server/qwen-code-cli.js +410 -410
- package/server/qwen-response-handler.js +73 -73
- package/server/routes/agent.js +1435 -1435
- package/server/routes/auth.js +159 -159
- package/server/routes/codex.js +20 -20
- package/server/routes/commands.js +570 -570
- package/server/routes/cursor.js +61 -61
- package/server/routes/diagnostics.js +41 -41
- package/server/routes/gemini.js +25 -25
- package/server/routes/git.js +1650 -1650
- package/server/routes/live-view.js +411 -411
- package/server/routes/mcp-utils.js +13 -13
- package/server/routes/messages.js +62 -62
- package/server/routes/network.js +125 -125
- package/server/routes/platformization.js +212 -212
- package/server/routes/plugins.js +320 -320
- package/server/routes/production-agent-loop.js +90 -90
- package/server/routes/projects.js +917 -917
- package/server/routes/public-api.js +34 -34
- package/server/routes/qwen.js +27 -27
- package/server/routes/remote.js +55 -55
- package/server/routes/settings.js +321 -321
- package/server/routes/telegram.js +140 -140
- package/server/routes/user.js +125 -125
- package/server/routes/webhooks.js +63 -63
- package/server/services/control-room.js +102 -102
- package/server/services/diagnostics.js +165 -165
- package/server/services/external-access.js +375 -375
- package/server/services/hermes-gateway.js +1562 -1247
- package/server/services/hermes-install-jobs.js +729 -729
- package/server/services/install-jobs.js +715 -715
- package/server/services/live-view.js +956 -956
- package/server/services/managed-runtimes.js +493 -493
- package/server/services/model-registry.js +144 -144
- package/server/services/notification-orchestrator.js +365 -365
- package/server/services/notification-taxonomy.js +204 -204
- package/server/services/platformization.js +815 -815
- package/server/services/production-agent-loop.js +248 -248
- package/server/services/provider-cli-versions.js +149 -149
- package/server/services/provider-credentials.js +189 -189
- package/server/services/provider-models.js +396 -396
- package/server/services/public-api-manifest.js +190 -182
- package/server/services/remote-connection.js +127 -127
- package/server/services/runtime-manager.js +323 -323
- package/server/services/startup-update.js +234 -234
- package/server/services/telegram/bot.js +331 -331
- package/server/services/telegram/control-center.js +979 -979
- package/server/services/telegram/telegram-http-client.js +151 -151
- package/server/services/telegram/translations.js +340 -340
- package/server/services/vapid-keys.js +36 -36
- package/server/services/webhooks.js +216 -216
- package/server/sessionManager.js +225 -225
- package/server/shared/interfaces.ts +54 -54
- package/server/shared/types.ts +172 -172
- package/server/shared/utils.ts +193 -193
- package/server/tsconfig.json +36 -36
- package/server/utils/colors.js +21 -21
- package/server/utils/commandParser.js +305 -305
- package/server/utils/frontmatter.js +18 -18
- package/server/utils/gitConfig.js +34 -34
- package/server/utils/plugin-loader.js +457 -457
- package/server/utils/plugin-process-manager.js +185 -185
- package/server/utils/port-access.js +209 -209
- package/server/utils/runtime-paths.js +37 -37
- package/server/utils/url-detection.js +71 -71
- package/server/vite-daemon.js +79 -79
- package/shared/modelConstants.js +161 -161
- package/shared/networkHosts.js +22 -22
- package/dist/assets/index-DMz0zv6T.css +0 -32
|
@@ -1,401 +1,401 @@
|
|
|
1
|
-
import os from 'node:os';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
|
|
4
|
-
export const PIXCODE_PERMISSION_POLICY_PROTOCOL = 'pixcode.permission-policy.v1' as const;
|
|
5
|
-
|
|
6
|
-
export const PERMISSION_CAPABILITIES = [
|
|
7
|
-
'shell',
|
|
8
|
-
'file_write',
|
|
9
|
-
'external_directory',
|
|
10
|
-
'network',
|
|
11
|
-
'secret',
|
|
12
|
-
] as const;
|
|
13
|
-
|
|
14
|
-
export const PERMISSION_POLICY_MODES = ['allow', 'deny', 'prompt', 'audit'] as const;
|
|
15
|
-
|
|
16
|
-
export type PermissionCapability = typeof PERMISSION_CAPABILITIES[number];
|
|
17
|
-
export type PermissionPolicyMode = typeof PERMISSION_POLICY_MODES[number];
|
|
18
|
-
export type PermissionDecisionStatus = 'allowed' | 'denied' | 'needs_approval';
|
|
19
|
-
|
|
20
|
-
export interface PermissionPolicy {
|
|
21
|
-
protocol: typeof PIXCODE_PERMISSION_POLICY_PROTOCOL;
|
|
22
|
-
modes: Record<PermissionCapability, PermissionPolicyMode>;
|
|
23
|
-
allowedExternalDirectories: string[];
|
|
24
|
-
audit: boolean;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export interface PermissionRequest {
|
|
28
|
-
requestId?: string;
|
|
29
|
-
source: 'workflow_node' | 'provider_tool' | 'api' | 'shell' | 'file' | string;
|
|
30
|
-
capability?: PermissionCapability;
|
|
31
|
-
capabilities?: PermissionCapability[];
|
|
32
|
-
toolName?: string;
|
|
33
|
-
command?: string;
|
|
34
|
-
input?: unknown;
|
|
35
|
-
cwd?: string;
|
|
36
|
-
workspacePath?: string;
|
|
37
|
-
targetPaths?: string[];
|
|
38
|
-
summary?: string;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export interface PermissionPolicyContext {
|
|
42
|
-
runId?: string;
|
|
43
|
-
nodeId?: string;
|
|
44
|
-
workflowId?: string;
|
|
45
|
-
adapterId?: string;
|
|
46
|
-
agentLabel?: string;
|
|
47
|
-
userId?: string | number | null;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export interface PermissionApprovalRequest {
|
|
51
|
-
id: string;
|
|
52
|
-
protocol: typeof PIXCODE_PERMISSION_POLICY_PROTOCOL;
|
|
53
|
-
status: 'pending' | 'allowed' | 'denied' | 'canceled';
|
|
54
|
-
capabilities: PermissionCapability[];
|
|
55
|
-
source: string;
|
|
56
|
-
toolName?: string;
|
|
57
|
-
runId?: string;
|
|
58
|
-
nodeId?: string;
|
|
59
|
-
workflowId?: string;
|
|
60
|
-
adapterId?: string;
|
|
61
|
-
agentLabel?: string;
|
|
62
|
-
summary?: string;
|
|
63
|
-
message: string;
|
|
64
|
-
createdAt: number;
|
|
65
|
-
resolvedAt?: number;
|
|
66
|
-
resolvedBy?: string | number | null;
|
|
67
|
-
resolutionMessage?: string;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
export interface PermissionPolicyEvent {
|
|
71
|
-
id: string;
|
|
72
|
-
protocol: typeof PIXCODE_PERMISSION_POLICY_PROTOCOL;
|
|
73
|
-
status: PermissionDecisionStatus;
|
|
74
|
-
behavior: 'allow' | 'deny' | 'prompt';
|
|
75
|
-
capabilities: PermissionCapability[];
|
|
76
|
-
source: string;
|
|
77
|
-
toolName?: string;
|
|
78
|
-
summary?: string;
|
|
79
|
-
message: string;
|
|
80
|
-
modeByCapability: Partial<Record<PermissionCapability, PermissionPolicyMode>>;
|
|
81
|
-
audit: boolean;
|
|
82
|
-
runId?: string;
|
|
83
|
-
nodeId?: string;
|
|
84
|
-
workflowId?: string;
|
|
85
|
-
adapterId?: string;
|
|
86
|
-
agentLabel?: string;
|
|
87
|
-
createdAt: number;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
export interface PermissionDecision {
|
|
91
|
-
protocol: typeof PIXCODE_PERMISSION_POLICY_PROTOCOL;
|
|
92
|
-
requestId: string;
|
|
93
|
-
status: PermissionDecisionStatus;
|
|
94
|
-
behavior: 'allow' | 'deny' | 'prompt';
|
|
95
|
-
capabilities: PermissionCapability[];
|
|
96
|
-
modeByCapability: Partial<Record<PermissionCapability, PermissionPolicyMode>>;
|
|
97
|
-
message: string;
|
|
98
|
-
audit: boolean;
|
|
99
|
-
event: PermissionPolicyEvent;
|
|
100
|
-
approvalRequest?: PermissionApprovalRequest;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
const DEFAULT_MODES: Record<PermissionCapability, PermissionPolicyMode> = {
|
|
104
|
-
shell: 'audit',
|
|
105
|
-
file_write: 'audit',
|
|
106
|
-
external_directory: 'audit',
|
|
107
|
-
network: 'audit',
|
|
108
|
-
secret: 'audit',
|
|
109
|
-
};
|
|
110
|
-
|
|
111
|
-
const NETWORK_COMMAND_PATTERN = /\b(curl|wget|fetch|ssh|scp|rsync|gh|git\s+(?:clone|fetch|pull|push)|npm\s+(?:install|publish|view|pack|audit)|pnpm\s+(?:install|publish)|yarn\s+(?:install|publish)|pip\s+install|cargo\s+(?:install|publish)|go\s+(?:get|install)|docker\s+(?:pull|push|run))\b|https?:\/\//iu;
|
|
112
|
-
const FILE_WRITE_COMMAND_PATTERN = /(^|\s)(apply_patch|touch|mkdir|rm|mv|cp|tee|sed\s+-i|perl\s+-pi|truncate|chmod|chown)\b|>>?|\b(write|edit|patch|delete|create)\s+(?:file|folder|directory)\b/iu;
|
|
113
|
-
const SECRET_COMMAND_PATTERN = /\b(printenv|env|set)\b|\b(cat|type|less|more)\s+(\S*\.env\b|\S*secret\S*|\S*token\S*|\S*key\S*)/iu;
|
|
114
|
-
const SECRET_VALUE_PATTERN = /\b(?:sk|ghp|github_pat|glpat|npm)_[A-Za-z0-9_=-]{12,}\b|(?:api[_-]?key|secret|token|password)\s*[:=]\s*['"]?[A-Za-z0-9_./+=-]{8,}/iu;
|
|
115
|
-
|
|
116
|
-
function readRecord(value: unknown): Record<string, unknown> | undefined {
|
|
117
|
-
return value && typeof value === 'object' ? value as Record<string, unknown> : undefined;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
function readString(value: unknown): string | undefined {
|
|
121
|
-
return typeof value === 'string' && value.trim() ? value.trim() : undefined;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
function readMode(value: unknown): PermissionPolicyMode | undefined {
|
|
125
|
-
return typeof value === 'string' && (PERMISSION_POLICY_MODES as readonly string[]).includes(value)
|
|
126
|
-
? value as PermissionPolicyMode
|
|
127
|
-
: undefined;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
function uniqueCapabilities(values: Array<PermissionCapability | undefined>): PermissionCapability[] {
|
|
131
|
-
return [...new Set(values.filter((value): value is PermissionCapability => Boolean(value)))];
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
function requestId(value?: string): string {
|
|
135
|
-
return value?.trim() || `perm_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
function normalizePath(value: string | undefined): string | undefined {
|
|
139
|
-
if (!value?.trim()) return undefined;
|
|
140
|
-
try {
|
|
141
|
-
return path.resolve(value);
|
|
142
|
-
} catch {
|
|
143
|
-
return undefined;
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
function isInsidePath(basePath: string | undefined, candidatePath: string | undefined): boolean {
|
|
148
|
-
const base = normalizePath(basePath);
|
|
149
|
-
const candidate = normalizePath(candidatePath);
|
|
150
|
-
if (!base || !candidate) return true;
|
|
151
|
-
if (candidate === base) return true;
|
|
152
|
-
const relative = path.relative(base, candidate);
|
|
153
|
-
return Boolean(relative && !relative.startsWith('..') && !path.isAbsolute(relative));
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
function collectInputPaths(input: unknown): string[] {
|
|
157
|
-
const paths: string[] = [];
|
|
158
|
-
const visit = (value: unknown) => {
|
|
159
|
-
if (typeof value === 'string') {
|
|
160
|
-
if (/^(?:[A-Za-z]:[\\/]|\/|~\/|\.\.?[\\/])/u.test(value.trim())) {
|
|
161
|
-
paths.push(value.trim().replace(/^~(?=$|[\\/])/u, os.homedir()));
|
|
162
|
-
}
|
|
163
|
-
return;
|
|
164
|
-
}
|
|
165
|
-
if (Array.isArray(value)) {
|
|
166
|
-
value.forEach(visit);
|
|
167
|
-
return;
|
|
168
|
-
}
|
|
169
|
-
const record = readRecord(value);
|
|
170
|
-
if (!record) return;
|
|
171
|
-
for (const [key, nested] of Object.entries(record)) {
|
|
172
|
-
if (/path|file|dir|cwd|target|source|destination/iu.test(key)) {
|
|
173
|
-
visit(nested);
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
};
|
|
177
|
-
visit(input);
|
|
178
|
-
return paths;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
function inferCapabilities(request: PermissionRequest): PermissionCapability[] {
|
|
182
|
-
const capabilities: PermissionCapability[] = [];
|
|
183
|
-
if (request.capability) capabilities.push(request.capability);
|
|
184
|
-
if (Array.isArray(request.capabilities)) capabilities.push(...request.capabilities);
|
|
185
|
-
|
|
186
|
-
const toolName = request.toolName?.toLocaleLowerCase('en') ?? '';
|
|
187
|
-
const command = [
|
|
188
|
-
request.command,
|
|
189
|
-
typeof request.input === 'string' ? request.input : undefined,
|
|
190
|
-
readString(readRecord(request.input)?.command),
|
|
191
|
-
readString(readRecord(request.input)?.query),
|
|
192
|
-
].filter(Boolean).join('\n');
|
|
193
|
-
const inputText = [
|
|
194
|
-
command,
|
|
195
|
-
typeof request.summary === 'string' ? request.summary : undefined,
|
|
196
|
-
typeof request.input === 'object' ? JSON.stringify(request.input) : undefined,
|
|
197
|
-
].filter(Boolean).join('\n');
|
|
198
|
-
|
|
199
|
-
if (toolName === 'bash' || toolName.includes('shell') || toolName.includes('terminal') || command.trim()) {
|
|
200
|
-
capabilities.push('shell');
|
|
201
|
-
}
|
|
202
|
-
if (/write|edit|multiedit|notebookedit|delete|patch/iu.test(toolName) || FILE_WRITE_COMMAND_PATTERN.test(command)) {
|
|
203
|
-
capabilities.push('file_write');
|
|
204
|
-
}
|
|
205
|
-
if (/webfetch|websearch|fetch|search/iu.test(toolName) || NETWORK_COMMAND_PATTERN.test(command)) {
|
|
206
|
-
capabilities.push('network');
|
|
207
|
-
}
|
|
208
|
-
if (/secret|credential|token|keychain|vault/iu.test(toolName) || SECRET_COMMAND_PATTERN.test(command) || SECRET_VALUE_PATTERN.test(inputText)) {
|
|
209
|
-
capabilities.push('secret');
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
const targetPaths = [
|
|
213
|
-
...(Array.isArray(request.targetPaths) ? request.targetPaths : []),
|
|
214
|
-
...collectInputPaths(request.input),
|
|
215
|
-
];
|
|
216
|
-
if (targetPaths.some((targetPath) => !isInsidePath(request.workspacePath ?? request.cwd, targetPath))) {
|
|
217
|
-
capabilities.push('external_directory');
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
return uniqueCapabilities(capabilities);
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
export const DEFAULT_PERMISSION_POLICY: PermissionPolicy = {
|
|
224
|
-
protocol: PIXCODE_PERMISSION_POLICY_PROTOCOL,
|
|
225
|
-
modes: DEFAULT_MODES,
|
|
226
|
-
allowedExternalDirectories: [],
|
|
227
|
-
audit: true,
|
|
228
|
-
};
|
|
229
|
-
|
|
230
|
-
export function normalizePermissionPolicy(value?: unknown): PermissionPolicy {
|
|
231
|
-
const record = readRecord(value);
|
|
232
|
-
const modeRecord = readRecord(record?.modes) ?? readRecord(record?.rules) ?? record ?? {};
|
|
233
|
-
const modes = { ...DEFAULT_MODES };
|
|
234
|
-
for (const capability of PERMISSION_CAPABILITIES) {
|
|
235
|
-
modes[capability] = readMode(modeRecord[capability]) ?? modes[capability];
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
const allowedExternalDirectories = Array.isArray(record?.allowedExternalDirectories)
|
|
239
|
-
? record.allowedExternalDirectories
|
|
240
|
-
.map((item) => readString(item))
|
|
241
|
-
.filter((item): item is string => Boolean(item))
|
|
242
|
-
: [];
|
|
243
|
-
|
|
244
|
-
return {
|
|
245
|
-
protocol: PIXCODE_PERMISSION_POLICY_PROTOCOL,
|
|
246
|
-
modes,
|
|
247
|
-
allowedExternalDirectories,
|
|
248
|
-
audit: typeof record?.audit === 'boolean' ? record.audit : true,
|
|
249
|
-
};
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
export function resolvePermissionPolicyFromMetadata(metadata?: Record<string, unknown>): PermissionPolicy {
|
|
253
|
-
const settings = readRecord(metadata?.settings);
|
|
254
|
-
return normalizePermissionPolicy(metadata?.permissionPolicy ?? settings?.permissionPolicy);
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
export function redactPermissionText(
|
|
258
|
-
value: string | undefined,
|
|
259
|
-
context?: { workspacePath?: string; cwd?: string; projectPath?: string },
|
|
260
|
-
): string | undefined {
|
|
261
|
-
if (!value?.trim()) return undefined;
|
|
262
|
-
const candidates = [
|
|
263
|
-
os.homedir(),
|
|
264
|
-
context?.workspacePath,
|
|
265
|
-
context?.cwd,
|
|
266
|
-
context?.projectPath,
|
|
267
|
-
].filter((item): item is string => Boolean(item && item.length > 2));
|
|
268
|
-
|
|
269
|
-
let text = value;
|
|
270
|
-
for (const candidate of candidates) {
|
|
271
|
-
text = text.split(candidate).join('[workspace]');
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
return text
|
|
275
|
-
.replace(SECRET_VALUE_PATTERN, '[redacted-secret]')
|
|
276
|
-
.replace(/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/gu, '[redacted-email]')
|
|
277
|
-
.trim();
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
function modeAllowsExternalDirectory(
|
|
281
|
-
request: PermissionRequest,
|
|
282
|
-
policy: PermissionPolicy,
|
|
283
|
-
): boolean {
|
|
284
|
-
const targetPaths = [
|
|
285
|
-
...(Array.isArray(request.targetPaths) ? request.targetPaths : []),
|
|
286
|
-
...collectInputPaths(request.input),
|
|
287
|
-
];
|
|
288
|
-
if (!targetPaths.length || !policy.allowedExternalDirectories.length) return false;
|
|
289
|
-
|
|
290
|
-
return targetPaths.every((targetPath) =>
|
|
291
|
-
policy.allowedExternalDirectories.some((allowedPath) => isInsidePath(allowedPath, targetPath)),
|
|
292
|
-
);
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
export function createPermissionApprovalRequest(
|
|
296
|
-
decision: Omit<PermissionDecision, 'approvalRequest'>,
|
|
297
|
-
context?: PermissionPolicyContext,
|
|
298
|
-
): PermissionApprovalRequest {
|
|
299
|
-
return {
|
|
300
|
-
id: decision.requestId,
|
|
301
|
-
protocol: PIXCODE_PERMISSION_POLICY_PROTOCOL,
|
|
302
|
-
status: 'pending',
|
|
303
|
-
capabilities: decision.capabilities,
|
|
304
|
-
source: decision.event.source,
|
|
305
|
-
toolName: decision.event.toolName,
|
|
306
|
-
runId: context?.runId,
|
|
307
|
-
nodeId: context?.nodeId,
|
|
308
|
-
workflowId: context?.workflowId,
|
|
309
|
-
adapterId: context?.adapterId,
|
|
310
|
-
agentLabel: context?.agentLabel,
|
|
311
|
-
summary: decision.event.summary,
|
|
312
|
-
message: decision.message,
|
|
313
|
-
createdAt: decision.event.createdAt,
|
|
314
|
-
};
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
export function evaluatePermissionRequest({
|
|
318
|
-
policy: policyInput,
|
|
319
|
-
request,
|
|
320
|
-
context,
|
|
321
|
-
}: {
|
|
322
|
-
policy?: unknown;
|
|
323
|
-
request: PermissionRequest;
|
|
324
|
-
context?: PermissionPolicyContext;
|
|
325
|
-
}): PermissionDecision {
|
|
326
|
-
const policy = normalizePermissionPolicy(policyInput);
|
|
327
|
-
const id = requestId(request.requestId);
|
|
328
|
-
const capabilities = inferCapabilities(request);
|
|
329
|
-
const modeByCapability: Partial<Record<PermissionCapability, PermissionPolicyMode>> = {};
|
|
330
|
-
|
|
331
|
-
for (const capability of capabilities) {
|
|
332
|
-
const mode = capability === 'external_directory' && modeAllowsExternalDirectory(request, policy)
|
|
333
|
-
? 'allow'
|
|
334
|
-
: policy.modes[capability];
|
|
335
|
-
modeByCapability[capability] = mode;
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
const modes = Object.values(modeByCapability);
|
|
339
|
-
const behavior: PermissionDecision['behavior'] = modes.includes('deny')
|
|
340
|
-
? 'deny'
|
|
341
|
-
: modes.includes('prompt')
|
|
342
|
-
? 'prompt'
|
|
343
|
-
: 'allow';
|
|
344
|
-
const status: PermissionDecisionStatus = behavior === 'deny'
|
|
345
|
-
? 'denied'
|
|
346
|
-
: behavior === 'prompt'
|
|
347
|
-
? 'needs_approval'
|
|
348
|
-
: 'allowed';
|
|
349
|
-
const summary = redactPermissionText(
|
|
350
|
-
request.summary
|
|
351
|
-
?? request.command
|
|
352
|
-
?? (request.toolName ? `${request.toolName} tool request` : `${request.source} permission request`),
|
|
353
|
-
{
|
|
354
|
-
workspacePath: request.workspacePath,
|
|
355
|
-
cwd: request.cwd,
|
|
356
|
-
},
|
|
357
|
-
);
|
|
358
|
-
const capabilityText = capabilities.length ? capabilities.join(', ') : 'unclassified';
|
|
359
|
-
const message = behavior === 'deny'
|
|
360
|
-
? `Permission policy denied ${capabilityText}.`
|
|
361
|
-
: behavior === 'prompt'
|
|
362
|
-
? `Permission policy requires approval for ${capabilityText}.`
|
|
363
|
-
: `Permission policy allowed ${capabilityText}.`;
|
|
364
|
-
const event: PermissionPolicyEvent = {
|
|
365
|
-
id,
|
|
366
|
-
protocol: PIXCODE_PERMISSION_POLICY_PROTOCOL,
|
|
367
|
-
status,
|
|
368
|
-
behavior,
|
|
369
|
-
capabilities,
|
|
370
|
-
source: request.source,
|
|
371
|
-
toolName: request.toolName,
|
|
372
|
-
summary,
|
|
373
|
-
message,
|
|
374
|
-
modeByCapability,
|
|
375
|
-
audit: policy.audit || modes.includes('audit'),
|
|
376
|
-
runId: context?.runId,
|
|
377
|
-
nodeId: context?.nodeId,
|
|
378
|
-
workflowId: context?.workflowId,
|
|
379
|
-
adapterId: context?.adapterId,
|
|
380
|
-
agentLabel: context?.agentLabel,
|
|
381
|
-
createdAt: Date.now(),
|
|
382
|
-
};
|
|
383
|
-
const decisionBase: Omit<PermissionDecision, 'approvalRequest'> = {
|
|
384
|
-
protocol: PIXCODE_PERMISSION_POLICY_PROTOCOL,
|
|
385
|
-
requestId: id,
|
|
386
|
-
status,
|
|
387
|
-
behavior,
|
|
388
|
-
capabilities,
|
|
389
|
-
modeByCapability,
|
|
390
|
-
message,
|
|
391
|
-
audit: event.audit,
|
|
392
|
-
event,
|
|
393
|
-
};
|
|
394
|
-
|
|
395
|
-
return {
|
|
396
|
-
...decisionBase,
|
|
397
|
-
approvalRequest: behavior === 'prompt'
|
|
398
|
-
? createPermissionApprovalRequest(decisionBase, context)
|
|
399
|
-
: undefined,
|
|
400
|
-
};
|
|
401
|
-
}
|
|
1
|
+
import os from 'node:os';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
export const PIXCODE_PERMISSION_POLICY_PROTOCOL = 'pixcode.permission-policy.v1' as const;
|
|
5
|
+
|
|
6
|
+
export const PERMISSION_CAPABILITIES = [
|
|
7
|
+
'shell',
|
|
8
|
+
'file_write',
|
|
9
|
+
'external_directory',
|
|
10
|
+
'network',
|
|
11
|
+
'secret',
|
|
12
|
+
] as const;
|
|
13
|
+
|
|
14
|
+
export const PERMISSION_POLICY_MODES = ['allow', 'deny', 'prompt', 'audit'] as const;
|
|
15
|
+
|
|
16
|
+
export type PermissionCapability = typeof PERMISSION_CAPABILITIES[number];
|
|
17
|
+
export type PermissionPolicyMode = typeof PERMISSION_POLICY_MODES[number];
|
|
18
|
+
export type PermissionDecisionStatus = 'allowed' | 'denied' | 'needs_approval';
|
|
19
|
+
|
|
20
|
+
export interface PermissionPolicy {
|
|
21
|
+
protocol: typeof PIXCODE_PERMISSION_POLICY_PROTOCOL;
|
|
22
|
+
modes: Record<PermissionCapability, PermissionPolicyMode>;
|
|
23
|
+
allowedExternalDirectories: string[];
|
|
24
|
+
audit: boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface PermissionRequest {
|
|
28
|
+
requestId?: string;
|
|
29
|
+
source: 'workflow_node' | 'provider_tool' | 'api' | 'shell' | 'file' | string;
|
|
30
|
+
capability?: PermissionCapability;
|
|
31
|
+
capabilities?: PermissionCapability[];
|
|
32
|
+
toolName?: string;
|
|
33
|
+
command?: string;
|
|
34
|
+
input?: unknown;
|
|
35
|
+
cwd?: string;
|
|
36
|
+
workspacePath?: string;
|
|
37
|
+
targetPaths?: string[];
|
|
38
|
+
summary?: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface PermissionPolicyContext {
|
|
42
|
+
runId?: string;
|
|
43
|
+
nodeId?: string;
|
|
44
|
+
workflowId?: string;
|
|
45
|
+
adapterId?: string;
|
|
46
|
+
agentLabel?: string;
|
|
47
|
+
userId?: string | number | null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface PermissionApprovalRequest {
|
|
51
|
+
id: string;
|
|
52
|
+
protocol: typeof PIXCODE_PERMISSION_POLICY_PROTOCOL;
|
|
53
|
+
status: 'pending' | 'allowed' | 'denied' | 'canceled';
|
|
54
|
+
capabilities: PermissionCapability[];
|
|
55
|
+
source: string;
|
|
56
|
+
toolName?: string;
|
|
57
|
+
runId?: string;
|
|
58
|
+
nodeId?: string;
|
|
59
|
+
workflowId?: string;
|
|
60
|
+
adapterId?: string;
|
|
61
|
+
agentLabel?: string;
|
|
62
|
+
summary?: string;
|
|
63
|
+
message: string;
|
|
64
|
+
createdAt: number;
|
|
65
|
+
resolvedAt?: number;
|
|
66
|
+
resolvedBy?: string | number | null;
|
|
67
|
+
resolutionMessage?: string;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface PermissionPolicyEvent {
|
|
71
|
+
id: string;
|
|
72
|
+
protocol: typeof PIXCODE_PERMISSION_POLICY_PROTOCOL;
|
|
73
|
+
status: PermissionDecisionStatus;
|
|
74
|
+
behavior: 'allow' | 'deny' | 'prompt';
|
|
75
|
+
capabilities: PermissionCapability[];
|
|
76
|
+
source: string;
|
|
77
|
+
toolName?: string;
|
|
78
|
+
summary?: string;
|
|
79
|
+
message: string;
|
|
80
|
+
modeByCapability: Partial<Record<PermissionCapability, PermissionPolicyMode>>;
|
|
81
|
+
audit: boolean;
|
|
82
|
+
runId?: string;
|
|
83
|
+
nodeId?: string;
|
|
84
|
+
workflowId?: string;
|
|
85
|
+
adapterId?: string;
|
|
86
|
+
agentLabel?: string;
|
|
87
|
+
createdAt: number;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export interface PermissionDecision {
|
|
91
|
+
protocol: typeof PIXCODE_PERMISSION_POLICY_PROTOCOL;
|
|
92
|
+
requestId: string;
|
|
93
|
+
status: PermissionDecisionStatus;
|
|
94
|
+
behavior: 'allow' | 'deny' | 'prompt';
|
|
95
|
+
capabilities: PermissionCapability[];
|
|
96
|
+
modeByCapability: Partial<Record<PermissionCapability, PermissionPolicyMode>>;
|
|
97
|
+
message: string;
|
|
98
|
+
audit: boolean;
|
|
99
|
+
event: PermissionPolicyEvent;
|
|
100
|
+
approvalRequest?: PermissionApprovalRequest;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const DEFAULT_MODES: Record<PermissionCapability, PermissionPolicyMode> = {
|
|
104
|
+
shell: 'audit',
|
|
105
|
+
file_write: 'audit',
|
|
106
|
+
external_directory: 'audit',
|
|
107
|
+
network: 'audit',
|
|
108
|
+
secret: 'audit',
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const NETWORK_COMMAND_PATTERN = /\b(curl|wget|fetch|ssh|scp|rsync|gh|git\s+(?:clone|fetch|pull|push)|npm\s+(?:install|publish|view|pack|audit)|pnpm\s+(?:install|publish)|yarn\s+(?:install|publish)|pip\s+install|cargo\s+(?:install|publish)|go\s+(?:get|install)|docker\s+(?:pull|push|run))\b|https?:\/\//iu;
|
|
112
|
+
const FILE_WRITE_COMMAND_PATTERN = /(^|\s)(apply_patch|touch|mkdir|rm|mv|cp|tee|sed\s+-i|perl\s+-pi|truncate|chmod|chown)\b|>>?|\b(write|edit|patch|delete|create)\s+(?:file|folder|directory)\b/iu;
|
|
113
|
+
const SECRET_COMMAND_PATTERN = /\b(printenv|env|set)\b|\b(cat|type|less|more)\s+(\S*\.env\b|\S*secret\S*|\S*token\S*|\S*key\S*)/iu;
|
|
114
|
+
const SECRET_VALUE_PATTERN = /\b(?:sk|ghp|github_pat|glpat|npm)_[A-Za-z0-9_=-]{12,}\b|(?:api[_-]?key|secret|token|password)\s*[:=]\s*['"]?[A-Za-z0-9_./+=-]{8,}/iu;
|
|
115
|
+
|
|
116
|
+
function readRecord(value: unknown): Record<string, unknown> | undefined {
|
|
117
|
+
return value && typeof value === 'object' ? value as Record<string, unknown> : undefined;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function readString(value: unknown): string | undefined {
|
|
121
|
+
return typeof value === 'string' && value.trim() ? value.trim() : undefined;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function readMode(value: unknown): PermissionPolicyMode | undefined {
|
|
125
|
+
return typeof value === 'string' && (PERMISSION_POLICY_MODES as readonly string[]).includes(value)
|
|
126
|
+
? value as PermissionPolicyMode
|
|
127
|
+
: undefined;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function uniqueCapabilities(values: Array<PermissionCapability | undefined>): PermissionCapability[] {
|
|
131
|
+
return [...new Set(values.filter((value): value is PermissionCapability => Boolean(value)))];
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function requestId(value?: string): string {
|
|
135
|
+
return value?.trim() || `perm_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function normalizePath(value: string | undefined): string | undefined {
|
|
139
|
+
if (!value?.trim()) return undefined;
|
|
140
|
+
try {
|
|
141
|
+
return path.resolve(value);
|
|
142
|
+
} catch {
|
|
143
|
+
return undefined;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function isInsidePath(basePath: string | undefined, candidatePath: string | undefined): boolean {
|
|
148
|
+
const base = normalizePath(basePath);
|
|
149
|
+
const candidate = normalizePath(candidatePath);
|
|
150
|
+
if (!base || !candidate) return true;
|
|
151
|
+
if (candidate === base) return true;
|
|
152
|
+
const relative = path.relative(base, candidate);
|
|
153
|
+
return Boolean(relative && !relative.startsWith('..') && !path.isAbsolute(relative));
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function collectInputPaths(input: unknown): string[] {
|
|
157
|
+
const paths: string[] = [];
|
|
158
|
+
const visit = (value: unknown) => {
|
|
159
|
+
if (typeof value === 'string') {
|
|
160
|
+
if (/^(?:[A-Za-z]:[\\/]|\/|~\/|\.\.?[\\/])/u.test(value.trim())) {
|
|
161
|
+
paths.push(value.trim().replace(/^~(?=$|[\\/])/u, os.homedir()));
|
|
162
|
+
}
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
if (Array.isArray(value)) {
|
|
166
|
+
value.forEach(visit);
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
const record = readRecord(value);
|
|
170
|
+
if (!record) return;
|
|
171
|
+
for (const [key, nested] of Object.entries(record)) {
|
|
172
|
+
if (/path|file|dir|cwd|target|source|destination/iu.test(key)) {
|
|
173
|
+
visit(nested);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
visit(input);
|
|
178
|
+
return paths;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function inferCapabilities(request: PermissionRequest): PermissionCapability[] {
|
|
182
|
+
const capabilities: PermissionCapability[] = [];
|
|
183
|
+
if (request.capability) capabilities.push(request.capability);
|
|
184
|
+
if (Array.isArray(request.capabilities)) capabilities.push(...request.capabilities);
|
|
185
|
+
|
|
186
|
+
const toolName = request.toolName?.toLocaleLowerCase('en') ?? '';
|
|
187
|
+
const command = [
|
|
188
|
+
request.command,
|
|
189
|
+
typeof request.input === 'string' ? request.input : undefined,
|
|
190
|
+
readString(readRecord(request.input)?.command),
|
|
191
|
+
readString(readRecord(request.input)?.query),
|
|
192
|
+
].filter(Boolean).join('\n');
|
|
193
|
+
const inputText = [
|
|
194
|
+
command,
|
|
195
|
+
typeof request.summary === 'string' ? request.summary : undefined,
|
|
196
|
+
typeof request.input === 'object' ? JSON.stringify(request.input) : undefined,
|
|
197
|
+
].filter(Boolean).join('\n');
|
|
198
|
+
|
|
199
|
+
if (toolName === 'bash' || toolName.includes('shell') || toolName.includes('terminal') || command.trim()) {
|
|
200
|
+
capabilities.push('shell');
|
|
201
|
+
}
|
|
202
|
+
if (/write|edit|multiedit|notebookedit|delete|patch/iu.test(toolName) || FILE_WRITE_COMMAND_PATTERN.test(command)) {
|
|
203
|
+
capabilities.push('file_write');
|
|
204
|
+
}
|
|
205
|
+
if (/webfetch|websearch|fetch|search/iu.test(toolName) || NETWORK_COMMAND_PATTERN.test(command)) {
|
|
206
|
+
capabilities.push('network');
|
|
207
|
+
}
|
|
208
|
+
if (/secret|credential|token|keychain|vault/iu.test(toolName) || SECRET_COMMAND_PATTERN.test(command) || SECRET_VALUE_PATTERN.test(inputText)) {
|
|
209
|
+
capabilities.push('secret');
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const targetPaths = [
|
|
213
|
+
...(Array.isArray(request.targetPaths) ? request.targetPaths : []),
|
|
214
|
+
...collectInputPaths(request.input),
|
|
215
|
+
];
|
|
216
|
+
if (targetPaths.some((targetPath) => !isInsidePath(request.workspacePath ?? request.cwd, targetPath))) {
|
|
217
|
+
capabilities.push('external_directory');
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return uniqueCapabilities(capabilities);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export const DEFAULT_PERMISSION_POLICY: PermissionPolicy = {
|
|
224
|
+
protocol: PIXCODE_PERMISSION_POLICY_PROTOCOL,
|
|
225
|
+
modes: DEFAULT_MODES,
|
|
226
|
+
allowedExternalDirectories: [],
|
|
227
|
+
audit: true,
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
export function normalizePermissionPolicy(value?: unknown): PermissionPolicy {
|
|
231
|
+
const record = readRecord(value);
|
|
232
|
+
const modeRecord = readRecord(record?.modes) ?? readRecord(record?.rules) ?? record ?? {};
|
|
233
|
+
const modes = { ...DEFAULT_MODES };
|
|
234
|
+
for (const capability of PERMISSION_CAPABILITIES) {
|
|
235
|
+
modes[capability] = readMode(modeRecord[capability]) ?? modes[capability];
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const allowedExternalDirectories = Array.isArray(record?.allowedExternalDirectories)
|
|
239
|
+
? record.allowedExternalDirectories
|
|
240
|
+
.map((item) => readString(item))
|
|
241
|
+
.filter((item): item is string => Boolean(item))
|
|
242
|
+
: [];
|
|
243
|
+
|
|
244
|
+
return {
|
|
245
|
+
protocol: PIXCODE_PERMISSION_POLICY_PROTOCOL,
|
|
246
|
+
modes,
|
|
247
|
+
allowedExternalDirectories,
|
|
248
|
+
audit: typeof record?.audit === 'boolean' ? record.audit : true,
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
export function resolvePermissionPolicyFromMetadata(metadata?: Record<string, unknown>): PermissionPolicy {
|
|
253
|
+
const settings = readRecord(metadata?.settings);
|
|
254
|
+
return normalizePermissionPolicy(metadata?.permissionPolicy ?? settings?.permissionPolicy);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
export function redactPermissionText(
|
|
258
|
+
value: string | undefined,
|
|
259
|
+
context?: { workspacePath?: string; cwd?: string; projectPath?: string },
|
|
260
|
+
): string | undefined {
|
|
261
|
+
if (!value?.trim()) return undefined;
|
|
262
|
+
const candidates = [
|
|
263
|
+
os.homedir(),
|
|
264
|
+
context?.workspacePath,
|
|
265
|
+
context?.cwd,
|
|
266
|
+
context?.projectPath,
|
|
267
|
+
].filter((item): item is string => Boolean(item && item.length > 2));
|
|
268
|
+
|
|
269
|
+
let text = value;
|
|
270
|
+
for (const candidate of candidates) {
|
|
271
|
+
text = text.split(candidate).join('[workspace]');
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
return text
|
|
275
|
+
.replace(SECRET_VALUE_PATTERN, '[redacted-secret]')
|
|
276
|
+
.replace(/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/gu, '[redacted-email]')
|
|
277
|
+
.trim();
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function modeAllowsExternalDirectory(
|
|
281
|
+
request: PermissionRequest,
|
|
282
|
+
policy: PermissionPolicy,
|
|
283
|
+
): boolean {
|
|
284
|
+
const targetPaths = [
|
|
285
|
+
...(Array.isArray(request.targetPaths) ? request.targetPaths : []),
|
|
286
|
+
...collectInputPaths(request.input),
|
|
287
|
+
];
|
|
288
|
+
if (!targetPaths.length || !policy.allowedExternalDirectories.length) return false;
|
|
289
|
+
|
|
290
|
+
return targetPaths.every((targetPath) =>
|
|
291
|
+
policy.allowedExternalDirectories.some((allowedPath) => isInsidePath(allowedPath, targetPath)),
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
export function createPermissionApprovalRequest(
|
|
296
|
+
decision: Omit<PermissionDecision, 'approvalRequest'>,
|
|
297
|
+
context?: PermissionPolicyContext,
|
|
298
|
+
): PermissionApprovalRequest {
|
|
299
|
+
return {
|
|
300
|
+
id: decision.requestId,
|
|
301
|
+
protocol: PIXCODE_PERMISSION_POLICY_PROTOCOL,
|
|
302
|
+
status: 'pending',
|
|
303
|
+
capabilities: decision.capabilities,
|
|
304
|
+
source: decision.event.source,
|
|
305
|
+
toolName: decision.event.toolName,
|
|
306
|
+
runId: context?.runId,
|
|
307
|
+
nodeId: context?.nodeId,
|
|
308
|
+
workflowId: context?.workflowId,
|
|
309
|
+
adapterId: context?.adapterId,
|
|
310
|
+
agentLabel: context?.agentLabel,
|
|
311
|
+
summary: decision.event.summary,
|
|
312
|
+
message: decision.message,
|
|
313
|
+
createdAt: decision.event.createdAt,
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
export function evaluatePermissionRequest({
|
|
318
|
+
policy: policyInput,
|
|
319
|
+
request,
|
|
320
|
+
context,
|
|
321
|
+
}: {
|
|
322
|
+
policy?: unknown;
|
|
323
|
+
request: PermissionRequest;
|
|
324
|
+
context?: PermissionPolicyContext;
|
|
325
|
+
}): PermissionDecision {
|
|
326
|
+
const policy = normalizePermissionPolicy(policyInput);
|
|
327
|
+
const id = requestId(request.requestId);
|
|
328
|
+
const capabilities = inferCapabilities(request);
|
|
329
|
+
const modeByCapability: Partial<Record<PermissionCapability, PermissionPolicyMode>> = {};
|
|
330
|
+
|
|
331
|
+
for (const capability of capabilities) {
|
|
332
|
+
const mode = capability === 'external_directory' && modeAllowsExternalDirectory(request, policy)
|
|
333
|
+
? 'allow'
|
|
334
|
+
: policy.modes[capability];
|
|
335
|
+
modeByCapability[capability] = mode;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
const modes = Object.values(modeByCapability);
|
|
339
|
+
const behavior: PermissionDecision['behavior'] = modes.includes('deny')
|
|
340
|
+
? 'deny'
|
|
341
|
+
: modes.includes('prompt')
|
|
342
|
+
? 'prompt'
|
|
343
|
+
: 'allow';
|
|
344
|
+
const status: PermissionDecisionStatus = behavior === 'deny'
|
|
345
|
+
? 'denied'
|
|
346
|
+
: behavior === 'prompt'
|
|
347
|
+
? 'needs_approval'
|
|
348
|
+
: 'allowed';
|
|
349
|
+
const summary = redactPermissionText(
|
|
350
|
+
request.summary
|
|
351
|
+
?? request.command
|
|
352
|
+
?? (request.toolName ? `${request.toolName} tool request` : `${request.source} permission request`),
|
|
353
|
+
{
|
|
354
|
+
workspacePath: request.workspacePath,
|
|
355
|
+
cwd: request.cwd,
|
|
356
|
+
},
|
|
357
|
+
);
|
|
358
|
+
const capabilityText = capabilities.length ? capabilities.join(', ') : 'unclassified';
|
|
359
|
+
const message = behavior === 'deny'
|
|
360
|
+
? `Permission policy denied ${capabilityText}.`
|
|
361
|
+
: behavior === 'prompt'
|
|
362
|
+
? `Permission policy requires approval for ${capabilityText}.`
|
|
363
|
+
: `Permission policy allowed ${capabilityText}.`;
|
|
364
|
+
const event: PermissionPolicyEvent = {
|
|
365
|
+
id,
|
|
366
|
+
protocol: PIXCODE_PERMISSION_POLICY_PROTOCOL,
|
|
367
|
+
status,
|
|
368
|
+
behavior,
|
|
369
|
+
capabilities,
|
|
370
|
+
source: request.source,
|
|
371
|
+
toolName: request.toolName,
|
|
372
|
+
summary,
|
|
373
|
+
message,
|
|
374
|
+
modeByCapability,
|
|
375
|
+
audit: policy.audit || modes.includes('audit'),
|
|
376
|
+
runId: context?.runId,
|
|
377
|
+
nodeId: context?.nodeId,
|
|
378
|
+
workflowId: context?.workflowId,
|
|
379
|
+
adapterId: context?.adapterId,
|
|
380
|
+
agentLabel: context?.agentLabel,
|
|
381
|
+
createdAt: Date.now(),
|
|
382
|
+
};
|
|
383
|
+
const decisionBase: Omit<PermissionDecision, 'approvalRequest'> = {
|
|
384
|
+
protocol: PIXCODE_PERMISSION_POLICY_PROTOCOL,
|
|
385
|
+
requestId: id,
|
|
386
|
+
status,
|
|
387
|
+
behavior,
|
|
388
|
+
capabilities,
|
|
389
|
+
modeByCapability,
|
|
390
|
+
message,
|
|
391
|
+
audit: event.audit,
|
|
392
|
+
event,
|
|
393
|
+
};
|
|
394
|
+
|
|
395
|
+
return {
|
|
396
|
+
...decisionBase,
|
|
397
|
+
approvalRequest: behavior === 'prompt'
|
|
398
|
+
? createPermissionApprovalRequest(decisionBase, context)
|
|
399
|
+
: undefined,
|
|
400
|
+
};
|
|
401
|
+
}
|