@agenticmail/enterprise 0.5.312 → 0.5.313
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/README.md +682 -544
- package/dist/agent-autonomy-PSXQ4MNP.js +766 -0
- package/dist/agent-heartbeat-6H3YAQ32.js +510 -0
- package/dist/agent-heartbeat-7WS3XILF.js +510 -0
- package/dist/agent-heartbeat-BFGKYUUK.js +510 -0
- package/dist/agent-heartbeat-SSV65YTX.js +510 -0
- package/dist/agent-heartbeat-T5IIHVF4.js +510 -0
- package/dist/agent-heartbeat-X3C6FIU2.js +510 -0
- package/dist/agent-tools-BW6CLQQ7.js +13897 -0
- package/dist/agent-tools-KEA7QEWF.js +13897 -0
- package/dist/agent-tools-NU7V3S5N.js +13899 -0
- package/dist/agent-tools-WINDYRQ2.js +13897 -0
- package/dist/chunk-3ELH5CU6.js +4910 -0
- package/dist/chunk-4QYRS3MS.js +1519 -0
- package/dist/chunk-52REEVDW.js +1519 -0
- package/dist/chunk-5RZJ76YI.js +4977 -0
- package/dist/chunk-6L7FQI5Q.js +4909 -0
- package/dist/chunk-763OMGFI.js +1519 -0
- package/dist/chunk-7ILSXGY6.js +1519 -0
- package/dist/chunk-7UCKD25B.js +551 -0
- package/dist/chunk-A6MSR7DL.js +4977 -0
- package/dist/chunk-ASD2YB6O.js +1519 -0
- package/dist/chunk-AZOIHLLX.js +4977 -0
- package/dist/chunk-BDCFOP7O.js +537 -0
- package/dist/chunk-BSVWPG6I.js +106 -0
- package/dist/chunk-C46DNDZB.js +1519 -0
- package/dist/chunk-CFVTK4FQ.js +4977 -0
- package/dist/chunk-CHJAOKCJ.js +4921 -0
- package/dist/chunk-CYEWTXYH.js +4977 -0
- package/dist/chunk-D3KFSWLK.js +48 -0
- package/dist/chunk-DUVGNAIY.js +4977 -0
- package/dist/chunk-DX4XEFVE.js +25229 -0
- package/dist/chunk-EX6FQSEV.js +167 -0
- package/dist/chunk-F5VZ5EUH.js +1519 -0
- package/dist/chunk-FVUDSPOD.js +4977 -0
- package/dist/chunk-G6FTZKJX.js +4977 -0
- package/dist/chunk-GFEAZN6Y.js +1519 -0
- package/dist/chunk-HKV4FQFW.js +1519 -0
- package/dist/chunk-ICCPULDT.js +25217 -0
- package/dist/chunk-IYEM627Q.js +25216 -0
- package/dist/chunk-JHRJ4QJ6.js +1519 -0
- package/dist/chunk-K2DAUYHV.js +4977 -0
- package/dist/chunk-KDQDSZZQ.js +4973 -0
- package/dist/chunk-LDUD6AZY.js +1519 -0
- package/dist/chunk-LES5TJ5L.js +4909 -0
- package/dist/chunk-MJGGW6MC.js +106 -0
- package/dist/chunk-MQKIWAHQ.js +106 -0
- package/dist/chunk-NGA7BBPF.js +48 -0
- package/dist/chunk-OE3TI4IQ.js +1519 -0
- package/dist/chunk-OHSBIYDR.js +4977 -0
- package/dist/chunk-OZEYDEPB.js +1519 -0
- package/dist/chunk-P4PODSQH.js +1519 -0
- package/dist/chunk-P7UOSFIE.js +636 -0
- package/dist/chunk-PFN6DODU.js +4921 -0
- package/dist/chunk-PKDVM4IY.js +4917 -0
- package/dist/chunk-Q5KG3G7U.js +25115 -0
- package/dist/chunk-QMVNW4FJ.js +25229 -0
- package/dist/chunk-QZ5UPRBE.js +4977 -0
- package/dist/chunk-SPP23N42.js +4977 -0
- package/dist/chunk-SRGHNFOY.js +4921 -0
- package/dist/chunk-TPLVQFXM.js +2594 -0
- package/dist/chunk-U3XYF4QP.js +4977 -0
- package/dist/chunk-VRRJH2DY.js +4921 -0
- package/dist/chunk-WY42BS3F.js +1519 -0
- package/dist/chunk-XAA4VHHZ.js +1519 -0
- package/dist/chunk-Z5Y5KTPC.js +4977 -0
- package/dist/chunk-ZA4QRACH.js +4977 -0
- package/dist/chunk-ZHLGSTXF.js +4909 -0
- package/dist/cli-agent-26BUULHZ.js +2169 -0
- package/dist/cli-agent-2FLJWXOC.js +2169 -0
- package/dist/cli-agent-4NNQFLO6.js +2255 -0
- package/dist/cli-agent-5WV3EEPW.js +2252 -0
- package/dist/cli-agent-65JUT6DU.js +2193 -0
- package/dist/cli-agent-6HLL7A5K.js +2255 -0
- package/dist/cli-agent-CZ26QWUZ.js +2210 -0
- package/dist/cli-agent-HPVSWDNQ.js +2255 -0
- package/dist/cli-agent-K4SBVG5X.js +2210 -0
- package/dist/cli-agent-K5D424X2.js +2252 -0
- package/dist/cli-agent-U4OL5FGK.js +2210 -0
- package/dist/cli-agent-WUMPOIKQ.js +2169 -0
- package/dist/cli-agent-WWRGGJ2F.js +2255 -0
- package/dist/cli-agent-ZDBBTVGU.js +2193 -0
- package/dist/cli-agent-ZIZ5JP4O.js +2252 -0
- package/dist/cli-recover-I4KNR2OI.js +487 -0
- package/dist/cli-recover-IQTUKWR2.js +487 -0
- package/dist/cli-recover-OYJHELOR.js +487 -0
- package/dist/cli-recover-PVQC7UXB.js +487 -0
- package/dist/cli-recover-T32NABFA.js +487 -0
- package/dist/cli-serve-FTQJ3RUK.js +143 -0
- package/dist/cli-serve-G4PUCASH.js +143 -0
- package/dist/cli-serve-HBZYUUQ3.js +143 -0
- package/dist/cli-serve-L3NUROMO.js +143 -0
- package/dist/cli-serve-LAA5WIZK.js +143 -0
- package/dist/cli-serve-LV4TUSJD.js +143 -0
- package/dist/cli-serve-MFCTVA2L.js +140 -0
- package/dist/cli-serve-QCRUFI5B.js +143 -0
- package/dist/cli-serve-S7OGQN4P.js +143 -0
- package/dist/cli-serve-SI4BQRXT.js +140 -0
- package/dist/cli-serve-UNB7EHN4.js +143 -0
- package/dist/cli-serve-UV3GVDRD.js +143 -0
- package/dist/cli-serve-V5QICXR5.js +143 -0
- package/dist/cli-serve-VG6Z6GIB.js +143 -0
- package/dist/cli-serve-XSYHPGZI.js +143 -0
- package/dist/cli-serve-Y534FCRV.js +140 -0
- package/dist/cli-verify-CZIITRED.js +149 -0
- package/dist/cli-verify-N73GOKEF.js +149 -0
- package/dist/cli-verify-QEEBZOUZ.js +149 -0
- package/dist/cli-verify-RC5HI6DU.js +149 -0
- package/dist/cli-verify-VKBNIEAX.js +149 -0
- package/dist/cli.js +5 -5
- package/dist/dashboard/app.js +8 -2
- package/dist/dashboard/components/org-switcher.js +5 -1
- package/dist/dashboard/org-switcher.js +156 -0
- package/dist/dashboard/pages/login.js +160 -4
- package/dist/dashboard/pages/task-pipeline.js +1 -1
- package/dist/factory-3IWXVE37.js +9 -0
- package/dist/factory-5M6PTMLC.js +11 -0
- package/dist/factory-CSSHN7GE.js +11 -0
- package/dist/factory-JFWXTAWK.js +11 -0
- package/dist/factory-TBGUYM5X.js +9 -0
- package/dist/google-W5AYGNUJ.js +33 -0
- package/dist/index.js +6 -6
- package/dist/meetings-FJ453ENF.js +12 -0
- package/dist/postgres-BCHZWRU3.js +832 -0
- package/dist/postgres-BI4QVRM6.js +825 -0
- package/dist/postgres-BOTHOPDW.js +875 -0
- package/dist/postgres-JBUKR3TA.js +873 -0
- package/dist/postgres-Z7QYSU6K.js +861 -0
- package/dist/routes-7QYAQTWA.js +90 -0
- package/dist/routes-JCBVZU54.js +90 -0
- package/dist/routes-KEDEJFRE.js +90 -0
- package/dist/routes-WI64ADVH.js +90 -0
- package/dist/routes-X36OSCID.js +90 -0
- package/dist/runtime-75XR6KEW.js +45 -0
- package/dist/runtime-BNM7ZNNL.js +45 -0
- package/dist/runtime-ES6WCJ7D.js +45 -0
- package/dist/runtime-KYJTML2B.js +45 -0
- package/dist/runtime-LO67ZHQA.js +45 -0
- package/dist/runtime-VIXKKVSZ.js +45 -0
- package/dist/runtime-WHWJPCGK.js +45 -0
- package/dist/runtime-Z2Q6GUHH.js +45 -0
- package/dist/runtime-ZZ6CALSB.js +45 -0
- package/dist/server-27A4WEJC.js +28 -0
- package/dist/server-2CBXP4WS.js +28 -0
- package/dist/server-4JQAB5R4.js +28 -0
- package/dist/server-6BOM5U64.js +28 -0
- package/dist/server-FLJKNPRD.js +28 -0
- package/dist/server-HMIHIQ2N.js +28 -0
- package/dist/server-KIXXLR2D.js +28 -0
- package/dist/server-KSEIZTXF.js +28 -0
- package/dist/server-MPVW7DKZ.js +28 -0
- package/dist/server-PRTVRQ2D.js +28 -0
- package/dist/server-SYIG6HAX.js +28 -0
- package/dist/server-U32KDIXC.js +28 -0
- package/dist/server-WFN6CA4T.js +28 -0
- package/dist/server-XQUE3FGT.js +28 -0
- package/dist/server-XWT2UORK.js +28 -0
- package/dist/server-Y3BGNN5Q.js +28 -0
- package/dist/setup-352L2TPS.js +20 -0
- package/dist/setup-4MM645XK.js +20 -0
- package/dist/setup-5JPWW6IP.js +20 -0
- package/dist/setup-CUN6LVUV.js +20 -0
- package/dist/setup-D3YHPWPY.js +20 -0
- package/dist/setup-D4A5I6UM.js +20 -0
- package/dist/setup-DOPLXTB3.js +20 -0
- package/dist/setup-E3NSIM6B.js +20 -0
- package/dist/setup-E3V2D7NL.js +20 -0
- package/dist/setup-FSYPGI2C.js +20 -0
- package/dist/setup-G3RPKRG3.js +20 -0
- package/dist/setup-KJ77HNWK.js +20 -0
- package/dist/setup-LPSOY5V5.js +20 -0
- package/dist/setup-N3ODDSQE.js +20 -0
- package/dist/setup-NLDM3M2P.js +20 -0
- package/dist/setup-SWJMNDWF.js +20 -0
- package/dist/system-prompts-6OUTAMH6.js +41 -0
- package/dist/task-queue-YP2I54IA.js +9 -0
- package/dist/telegram-QRNGRT5M.js +17 -0
- package/dist/whatsapp-VYVINCGV.js +31 -0
- package/god_is_great.html +35 -0
- package/package.json +1 -1
- package/src/agent-tools/index.ts +4 -1
- package/src/agent-tools/tool-resolver.ts +15 -4
- package/src/agent-tools/tools/browser.ts +2 -2
- package/src/agent-tools/tools/local/dependency-manager.ts +286 -0
- package/src/agent-tools/tools/local/index.ts +3 -0
- package/src/agent-tools/tools/messaging/telegram.ts +29 -0
- package/src/agent-tools/tools/messaging/whatsapp.ts +59 -4
- package/src/auth/routes.ts +1 -1
- package/src/cli-agent.ts +47 -6
- package/src/cli-serve.ts +2 -5
- package/src/dashboard/app.js +8 -2
- package/src/dashboard/components/org-switcher.js +5 -1
- package/src/dashboard/pages/login.js +160 -4
- package/src/dashboard/pages/task-pipeline.js +1 -1
- package/src/db/adapter.ts +2 -0
- package/src/db/factory.ts +78 -0
- package/src/db/postgres.ts +57 -12
- package/src/engine/agent-autonomy.ts +1 -1
- package/src/engine/agent-heartbeat.ts +1 -1
- package/src/engine/messaging-poller.ts +146 -11
- package/src/engine/oauth-connect-routes.ts +23 -3
- package/src/engine/routes.ts +1 -1
- package/src/engine/task-poller.ts +54 -3
- package/src/engine/task-queue.ts +30 -0
- package/src/runtime/index.ts +2 -1
- package/src/runtime/types.ts +2 -0
- package/src/server.ts +13 -1
- package/src/system-prompts/triage.ts +1 -1
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dependency Manager — Detects missing tools/packages and handles installation.
|
|
3
|
+
*
|
|
4
|
+
* Agents use this to:
|
|
5
|
+
* 1. Check if a command/tool is available before using it
|
|
6
|
+
* 2. Request installation if missing (with manager approval flow)
|
|
7
|
+
* 3. Track what was installed for cleanup
|
|
8
|
+
*
|
|
9
|
+
* Installation approval modes:
|
|
10
|
+
* - 'auto': Install without asking (for trusted/common tools)
|
|
11
|
+
* - 'ask_manager': Message manager for approval before installing
|
|
12
|
+
* - 'deny': Never install (locked-down environments)
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { exec as cpExec } from 'node:child_process';
|
|
16
|
+
import { promisify } from 'node:util';
|
|
17
|
+
import { platform } from 'node:os';
|
|
18
|
+
import type { ToolDefinition } from '../../types.js';
|
|
19
|
+
|
|
20
|
+
var execAsync = promisify(cpExec);
|
|
21
|
+
|
|
22
|
+
// Track installations per session for cleanup
|
|
23
|
+
var sessionInstalls = new Map<string, string[]>();
|
|
24
|
+
|
|
25
|
+
// Common tools and their package names per platform
|
|
26
|
+
var KNOWN_PACKAGES: Record<string, { brew?: string; apt?: string; pip?: string; npm?: string; description: string }> = {
|
|
27
|
+
ffmpeg: { brew: 'ffmpeg', apt: 'ffmpeg', description: 'Audio/video processing toolkit' },
|
|
28
|
+
ffprobe: { brew: 'ffmpeg', apt: 'ffmpeg', description: 'Audio/video analysis (part of ffmpeg)' },
|
|
29
|
+
convert: { brew: 'imagemagick', apt: 'imagemagick', description: 'Image manipulation (ImageMagick)' },
|
|
30
|
+
magick: { brew: 'imagemagick', apt: 'imagemagick', description: 'Image manipulation (ImageMagick)' },
|
|
31
|
+
sox: { brew: 'sox', apt: 'sox', description: 'Sound processing toolkit' },
|
|
32
|
+
jq: { brew: 'jq', apt: 'jq', description: 'JSON processor' },
|
|
33
|
+
curl: { brew: 'curl', apt: 'curl', description: 'HTTP client' },
|
|
34
|
+
wget: { brew: 'wget', apt: 'wget', description: 'File downloader' },
|
|
35
|
+
yt_dlp: { brew: 'yt-dlp', pip: 'yt-dlp', description: 'Video downloader' },
|
|
36
|
+
'yt-dlp': { brew: 'yt-dlp', pip: 'yt-dlp', description: 'Video downloader' },
|
|
37
|
+
tesseract: { brew: 'tesseract', apt: 'tesseract-ocr', description: 'OCR text recognition' },
|
|
38
|
+
pdftk: { brew: 'pdftk-java', apt: 'pdftk', description: 'PDF toolkit' },
|
|
39
|
+
ghostscript: { brew: 'ghostscript', apt: 'ghostscript', description: 'PDF/PostScript processor' },
|
|
40
|
+
gs: { brew: 'ghostscript', apt: 'ghostscript', description: 'PDF/PostScript processor' },
|
|
41
|
+
chromium: { brew: 'chromium', apt: 'chromium-browser', description: 'Web browser' },
|
|
42
|
+
pandoc: { brew: 'pandoc', apt: 'pandoc', description: 'Document format converter' },
|
|
43
|
+
rsvg: { brew: 'librsvg', apt: 'librsvg2-bin', description: 'SVG renderer' },
|
|
44
|
+
graphviz: { brew: 'graphviz', apt: 'graphviz', description: 'Graph visualization' },
|
|
45
|
+
dot: { brew: 'graphviz', apt: 'graphviz', description: 'Graph visualization (part of graphviz)' },
|
|
46
|
+
gifsicle: { brew: 'gifsicle', apt: 'gifsicle', description: 'GIF optimizer' },
|
|
47
|
+
optipng: { brew: 'optipng', apt: 'optipng', description: 'PNG optimizer' },
|
|
48
|
+
qrencode: { brew: 'qrencode', apt: 'qrencode', description: 'QR code generator' },
|
|
49
|
+
htop: { brew: 'htop', apt: 'htop', description: 'Process monitor' },
|
|
50
|
+
tree: { brew: 'tree', apt: 'tree', description: 'Directory tree viewer' },
|
|
51
|
+
ripgrep: { brew: 'ripgrep', apt: 'ripgrep', description: 'Fast text search (rg)' },
|
|
52
|
+
rg: { brew: 'ripgrep', apt: 'ripgrep', description: 'Fast text search' },
|
|
53
|
+
fd: { brew: 'fd', apt: 'fd-find', description: 'Fast file finder' },
|
|
54
|
+
bat: { brew: 'bat', apt: 'bat', description: 'Better cat with syntax highlighting' },
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export function createDependencyManagerTools(): ToolDefinition[] {
|
|
58
|
+
return [
|
|
59
|
+
{
|
|
60
|
+
name: 'check_dependency',
|
|
61
|
+
description: `Check if a command-line tool is available on this system. Returns availability, version info, and install instructions if missing. Use this BEFORE running shell commands that depend on specific tools (ffmpeg, imagemagick, etc.). If the tool is missing, use install_dependency to install it.`,
|
|
62
|
+
input_schema: {
|
|
63
|
+
type: 'object' as const,
|
|
64
|
+
properties: {
|
|
65
|
+
command: { type: 'string', description: 'Command to check (e.g. "ffmpeg", "convert", "jq")' },
|
|
66
|
+
},
|
|
67
|
+
required: ['command'],
|
|
68
|
+
},
|
|
69
|
+
execute: async (input: any) => {
|
|
70
|
+
var cmd = input.command.trim();
|
|
71
|
+
try {
|
|
72
|
+
var { stdout } = await execAsync(`which ${cmd} 2>/dev/null && ${cmd} --version 2>&1 || ${cmd} -version 2>&1 || echo "version-unknown"`, {
|
|
73
|
+
timeout: 5000,
|
|
74
|
+
env: { ...process.env, TERM: 'dumb' },
|
|
75
|
+
});
|
|
76
|
+
return {
|
|
77
|
+
available: true,
|
|
78
|
+
command: cmd,
|
|
79
|
+
path: stdout.split('\n')[0] || '',
|
|
80
|
+
versionInfo: stdout.slice(0, 500),
|
|
81
|
+
};
|
|
82
|
+
} catch {
|
|
83
|
+
var known = KNOWN_PACKAGES[cmd];
|
|
84
|
+
var os = platform();
|
|
85
|
+
var installCmd = '';
|
|
86
|
+
if (known) {
|
|
87
|
+
if (os === 'darwin' && known.brew) installCmd = `brew install ${known.brew}`;
|
|
88
|
+
else if (known.apt) installCmd = `sudo apt-get install -y ${known.apt}`;
|
|
89
|
+
else if (known.pip) installCmd = `pip install ${known.pip}`;
|
|
90
|
+
else if (known.npm) installCmd = `npm install -g ${known.npm}`;
|
|
91
|
+
}
|
|
92
|
+
return {
|
|
93
|
+
available: false,
|
|
94
|
+
command: cmd,
|
|
95
|
+
description: known?.description || 'Unknown tool',
|
|
96
|
+
installCommand: installCmd || `Use shell_install with package="${cmd}"`,
|
|
97
|
+
suggestion: `Tool "${cmd}" is not installed. ${installCmd ? `Install with: ${installCmd}` : 'Use install_dependency to install it.'}`,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
name: 'install_dependency',
|
|
104
|
+
description: `Install a missing command-line tool using the system package manager (brew on macOS, apt on Linux). Auto-detects the correct package name for common tools. Tracks installations for cleanup. Use check_dependency first to verify the tool is actually missing.`,
|
|
105
|
+
input_schema: {
|
|
106
|
+
type: 'object' as const,
|
|
107
|
+
properties: {
|
|
108
|
+
command: { type: 'string', description: 'Tool name to install (e.g. "ffmpeg", "imagemagick")' },
|
|
109
|
+
package: { type: 'string', description: 'Override package name if different from command' },
|
|
110
|
+
method: { type: 'string', description: 'Force install method: brew|apt|pip|npm (auto-detected by default)' },
|
|
111
|
+
},
|
|
112
|
+
required: ['command'],
|
|
113
|
+
},
|
|
114
|
+
execute: async (input: any) => {
|
|
115
|
+
var cmd = input.command.trim();
|
|
116
|
+
var os = platform();
|
|
117
|
+
var known = KNOWN_PACKAGES[cmd];
|
|
118
|
+
var pkg = input.package || '';
|
|
119
|
+
var method = input.method || '';
|
|
120
|
+
|
|
121
|
+
// Determine install command
|
|
122
|
+
var installCmd = '';
|
|
123
|
+
var packageName = '';
|
|
124
|
+
|
|
125
|
+
if (method === 'pip' || (!method && known?.pip && !known?.brew && !known?.apt)) {
|
|
126
|
+
packageName = pkg || known?.pip || cmd;
|
|
127
|
+
installCmd = `pip install ${packageName}`;
|
|
128
|
+
} else if (method === 'npm' || (!method && known?.npm)) {
|
|
129
|
+
packageName = pkg || known?.npm || cmd;
|
|
130
|
+
installCmd = `npm install -g ${packageName}`;
|
|
131
|
+
} else if (os === 'darwin') {
|
|
132
|
+
packageName = pkg || known?.brew || cmd;
|
|
133
|
+
installCmd = `brew install ${packageName}`;
|
|
134
|
+
} else {
|
|
135
|
+
// Linux
|
|
136
|
+
packageName = pkg || known?.apt || cmd;
|
|
137
|
+
installCmd = `sudo apt-get install -y ${packageName}`;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
console.log(`[dep-manager] Installing: ${installCmd}`);
|
|
141
|
+
|
|
142
|
+
try {
|
|
143
|
+
var r = await execAsync(installCmd, {
|
|
144
|
+
timeout: 300000, // 5 min for large packages
|
|
145
|
+
maxBuffer: 2 * 1024 * 1024,
|
|
146
|
+
env: { ...process.env, DEBIAN_FRONTEND: 'noninteractive', TERM: 'dumb', HOMEBREW_NO_AUTO_UPDATE: '1' },
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
// Track installation for cleanup
|
|
150
|
+
var key = 'global'; // Could be per-session if we had session context
|
|
151
|
+
var installed = sessionInstalls.get(key) || [];
|
|
152
|
+
installed.push(packageName);
|
|
153
|
+
sessionInstalls.set(key, installed);
|
|
154
|
+
|
|
155
|
+
// Verify installation
|
|
156
|
+
try {
|
|
157
|
+
await execAsync(`which ${cmd}`, { timeout: 3000 });
|
|
158
|
+
return {
|
|
159
|
+
ok: true,
|
|
160
|
+
command: cmd,
|
|
161
|
+
package: packageName,
|
|
162
|
+
method: installCmd.split(' ')[0],
|
|
163
|
+
stdout: (r.stdout || '').slice(-2000),
|
|
164
|
+
note: `Installed "${packageName}" successfully. The "${cmd}" command is now available.`,
|
|
165
|
+
};
|
|
166
|
+
} catch {
|
|
167
|
+
return {
|
|
168
|
+
ok: true,
|
|
169
|
+
command: cmd,
|
|
170
|
+
package: packageName,
|
|
171
|
+
warning: `Package installed but "${cmd}" not found in PATH. The binary may have a different name.`,
|
|
172
|
+
stdout: (r.stdout || '').slice(-2000),
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
} catch (err: any) {
|
|
176
|
+
return {
|
|
177
|
+
ok: false,
|
|
178
|
+
command: cmd,
|
|
179
|
+
error: (err.stderr || err.message || '').slice(0, 5000),
|
|
180
|
+
suggestion: `Installation failed. Try: 1) Check package name is correct, 2) Run with sudo if needed, 3) Use shell_exec to install manually.`,
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
},
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
name: 'check_environment',
|
|
187
|
+
description: 'Check the current system environment: OS, package manager, installed common tools, available disk space, etc. Use at the start of complex tasks to understand capabilities.',
|
|
188
|
+
input_schema: {
|
|
189
|
+
type: 'object' as const,
|
|
190
|
+
properties: {},
|
|
191
|
+
required: [],
|
|
192
|
+
},
|
|
193
|
+
execute: async () => {
|
|
194
|
+
var os = platform();
|
|
195
|
+
var checks: Record<string, boolean> = {};
|
|
196
|
+
var commonTools = ['ffmpeg', 'convert', 'jq', 'curl', 'wget', 'sox', 'pandoc', 'tesseract', 'yt-dlp', 'rg', 'node', 'python3', 'pip3', 'git'];
|
|
197
|
+
|
|
198
|
+
for (var tool of commonTools) {
|
|
199
|
+
try {
|
|
200
|
+
await execAsync(`which ${tool}`, { timeout: 2000 });
|
|
201
|
+
checks[tool] = true;
|
|
202
|
+
} catch {
|
|
203
|
+
checks[tool] = false;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
var pkgManager = 'unknown';
|
|
208
|
+
try {
|
|
209
|
+
if (os === 'darwin') { await execAsync('which brew', { timeout: 2000 }); pkgManager = 'brew'; }
|
|
210
|
+
else { await execAsync('which apt-get', { timeout: 2000 }); pkgManager = 'apt'; }
|
|
211
|
+
} catch {
|
|
212
|
+
try { await execAsync('which dnf', { timeout: 2000 }); pkgManager = 'dnf'; }
|
|
213
|
+
catch { try { await execAsync('which pacman', { timeout: 2000 }); pkgManager = 'pacman'; } catch {} }
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
var diskSpace = '';
|
|
217
|
+
try {
|
|
218
|
+
var r = await execAsync('df -h / | tail -1', { timeout: 3000 });
|
|
219
|
+
diskSpace = r.stdout.trim();
|
|
220
|
+
} catch {}
|
|
221
|
+
|
|
222
|
+
var nodeVersion = '';
|
|
223
|
+
try { nodeVersion = (await execAsync('node --version', { timeout: 2000 })).stdout.trim(); } catch {}
|
|
224
|
+
|
|
225
|
+
return {
|
|
226
|
+
os,
|
|
227
|
+
arch: process.arch,
|
|
228
|
+
packageManager: pkgManager,
|
|
229
|
+
nodeVersion,
|
|
230
|
+
diskSpace,
|
|
231
|
+
tools: checks,
|
|
232
|
+
missingTools: Object.entries(checks).filter(([, v]) => !v).map(([k]) => k),
|
|
233
|
+
availableTools: Object.entries(checks).filter(([, v]) => v).map(([k]) => k),
|
|
234
|
+
installedThisSession: sessionInstalls.get('global') || [],
|
|
235
|
+
};
|
|
236
|
+
},
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
name: 'cleanup_installed',
|
|
240
|
+
description: 'List or uninstall packages that were installed during this session. Use for cleanup after completing tasks.',
|
|
241
|
+
input_schema: {
|
|
242
|
+
type: 'object' as const,
|
|
243
|
+
properties: {
|
|
244
|
+
action: { type: 'string', description: '"list" to see installed packages, "uninstall" to remove them, "keep" to clear tracking without removing' },
|
|
245
|
+
packages: { type: 'array', items: { type: 'string' }, description: 'Specific packages to uninstall (default: all session-installed)' },
|
|
246
|
+
},
|
|
247
|
+
required: ['action'],
|
|
248
|
+
},
|
|
249
|
+
execute: async (input: any) => {
|
|
250
|
+
var installed = sessionInstalls.get('global') || [];
|
|
251
|
+
|
|
252
|
+
if (input.action === 'list') {
|
|
253
|
+
return { installedThisSession: installed };
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (input.action === 'keep') {
|
|
257
|
+
sessionInstalls.delete('global');
|
|
258
|
+
return { ok: true, message: 'Tracking cleared. Packages remain installed.', packages: installed };
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (input.action === 'uninstall') {
|
|
262
|
+
var toRemove = input.packages || installed;
|
|
263
|
+
var os = platform();
|
|
264
|
+
var results: any[] = [];
|
|
265
|
+
|
|
266
|
+
for (var pkg of toRemove) {
|
|
267
|
+
try {
|
|
268
|
+
var cmd = os === 'darwin' ? `brew uninstall ${pkg}` : `sudo apt-get remove -y ${pkg}`;
|
|
269
|
+
await execAsync(cmd, { timeout: 60000, env: { ...process.env, DEBIAN_FRONTEND: 'noninteractive' } });
|
|
270
|
+
results.push({ package: pkg, removed: true });
|
|
271
|
+
// Remove from tracking
|
|
272
|
+
installed = installed.filter((p: string) => p !== pkg);
|
|
273
|
+
} catch (err: any) {
|
|
274
|
+
results.push({ package: pkg, removed: false, error: (err.message || '').slice(0, 200) });
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
sessionInstalls.set('global', installed);
|
|
279
|
+
return { results, remaining: installed };
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return { error: 'Unknown action. Use "list", "uninstall", or "keep".' };
|
|
283
|
+
},
|
|
284
|
+
},
|
|
285
|
+
];
|
|
286
|
+
}
|
|
@@ -11,6 +11,7 @@ import { createFileListTool } from './file-list.js';
|
|
|
11
11
|
import { createFileSearchTool } from './file-search.js';
|
|
12
12
|
import { createFileMoveTool, createFileDeleteTool } from './file-ops.js';
|
|
13
13
|
import { createShellTools } from './shell.js';
|
|
14
|
+
import { createDependencyManagerTools } from './dependency-manager.js';
|
|
14
15
|
import { createSystemInfoTool } from './system-info.js';
|
|
15
16
|
import { createCodingTools } from './coding.js';
|
|
16
17
|
import type { ToolDefinition } from '../../types.js';
|
|
@@ -36,6 +37,7 @@ export function createLocalSystemTools(config?: LocalToolsConfig): ToolDefinitio
|
|
|
36
37
|
createFileMoveTool(sandbox),
|
|
37
38
|
createFileDeleteTool(sandbox),
|
|
38
39
|
...createShellTools({ cwd: config?.shellCwd, timeout: config?.shellTimeout }),
|
|
40
|
+
...createDependencyManagerTools(),
|
|
39
41
|
createSystemInfoTool(),
|
|
40
42
|
...createCodingTools({ cwd: config?.shellCwd, sandbox }),
|
|
41
43
|
];
|
|
@@ -50,3 +52,4 @@ export { createFileMoveTool, createFileDeleteTool } from './file-ops.js';
|
|
|
50
52
|
export { createShellTools, createShellExecTool } from './shell.js';
|
|
51
53
|
export { createSystemInfoTool } from './system-info.js';
|
|
52
54
|
export { createCodingTools } from './coding.js';
|
|
55
|
+
export { createDependencyManagerTools } from './dependency-manager.js';
|
|
@@ -90,6 +90,35 @@ export function createTelegramTools(config: TelegramConfig): ToolDefinition[] {
|
|
|
90
90
|
input_schema: { type: 'object' as const, properties: {}, required: [] },
|
|
91
91
|
execute: async (_id: string) => tgApi(botToken, 'getMe'),
|
|
92
92
|
},
|
|
93
|
+
{
|
|
94
|
+
name: 'telegram_download_file',
|
|
95
|
+
description: 'Download a file from Telegram by file_id (from media messages). Returns the local path.',
|
|
96
|
+
input_schema: {
|
|
97
|
+
type: 'object' as const,
|
|
98
|
+
properties: {
|
|
99
|
+
fileId: { type: 'string', description: 'Telegram file_id from the message' },
|
|
100
|
+
fileName: { type: 'string', description: 'Optional output filename' },
|
|
101
|
+
},
|
|
102
|
+
required: ['fileId'],
|
|
103
|
+
},
|
|
104
|
+
execute: async (_id: string, input: any) => {
|
|
105
|
+
var fileData = await tgApi(botToken, 'getFile', { file_id: input.fileId });
|
|
106
|
+
if (!fileData.file_path) return { error: 'No file_path returned' };
|
|
107
|
+
var downloadUrl = `https://api.telegram.org/file/bot${botToken}/${fileData.file_path}`;
|
|
108
|
+
var resp = await fetch(downloadUrl);
|
|
109
|
+
if (!resp.ok) throw new Error(`Download failed: ${resp.status}`);
|
|
110
|
+
var { join } = await import('path');
|
|
111
|
+
var { mkdirSync, writeFileSync } = await import('fs');
|
|
112
|
+
var mediaDir = join('/tmp/agents/media');
|
|
113
|
+
try { mkdirSync(mediaDir, { recursive: true }); } catch {}
|
|
114
|
+
var ext = fileData.file_path.split('.').pop() || 'bin';
|
|
115
|
+
var localName = input.fileName || `telegram-${Date.now()}.${ext}`;
|
|
116
|
+
var localPath = join(mediaDir, localName);
|
|
117
|
+
var buffer = Buffer.from(await resp.arrayBuffer());
|
|
118
|
+
writeFileSync(localPath, buffer);
|
|
119
|
+
return { ok: true, localPath, size: buffer.length, originalPath: fileData.file_path };
|
|
120
|
+
},
|
|
121
|
+
},
|
|
93
122
|
{
|
|
94
123
|
name: 'telegram_get_chat',
|
|
95
124
|
description: 'Get chat/group details.',
|
|
@@ -155,22 +155,77 @@ export async function getOrCreateConnection(config: WhatsAppConfig): Promise<Wha
|
|
|
155
155
|
if (!isSelfChat) continue;
|
|
156
156
|
}
|
|
157
157
|
|
|
158
|
-
|
|
158
|
+
// Detect media
|
|
159
|
+
var mediaMessage = msg.message?.imageMessage || msg.message?.videoMessage ||
|
|
160
|
+
msg.message?.documentMessage || msg.message?.audioMessage ||
|
|
161
|
+
msg.message?.stickerMessage || null;
|
|
162
|
+
var hasMedia = !!mediaMessage;
|
|
163
|
+
|
|
164
|
+
// For media messages, caption may be the text
|
|
165
|
+
if (!text && mediaMessage) {
|
|
166
|
+
text = mediaMessage.caption || '';
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Skip if no text AND no media
|
|
170
|
+
if (!text && !hasMedia) continue;
|
|
159
171
|
|
|
160
172
|
var senderJid = isGroup ? msg.key.participant : remoteJid;
|
|
161
|
-
console.log(`[wa:${config.agentId.slice(0,8)}] Incoming from=${senderJid} group=${isGroup} selfChat=${!!isSelfChat} text="${text.slice(0,50)}"`);
|
|
173
|
+
console.log(`[wa:${config.agentId.slice(0,8)}] Incoming from=${senderJid} group=${isGroup} selfChat=${!!isSelfChat} hasMedia=${hasMedia} text="${(text||'').slice(0,50)}"`);
|
|
162
174
|
|
|
163
175
|
// Show typing indicator while agent processes
|
|
164
176
|
try { await sock.sendPresenceUpdate('composing', remoteJid); } catch {}
|
|
165
177
|
|
|
178
|
+
// Download media if present
|
|
179
|
+
var mediaPath: string | undefined;
|
|
180
|
+
var mediaType: string | undefined;
|
|
181
|
+
if (hasMedia && mediaMessage) {
|
|
182
|
+
try {
|
|
183
|
+
var { downloadMediaMessage } = await import('@whiskeysockets/baileys');
|
|
184
|
+
var buffer = await downloadMediaMessage(msg, 'buffer', {});
|
|
185
|
+
if (buffer && buffer.length > 0) {
|
|
186
|
+
var { join } = await import('path');
|
|
187
|
+
var { mkdirSync, writeFileSync } = await import('fs');
|
|
188
|
+
var mediaDir = join(config.dataDir || `/tmp/agents/${config.agentId}`, 'media');
|
|
189
|
+
try { mkdirSync(mediaDir, { recursive: true }); } catch {}
|
|
190
|
+
|
|
191
|
+
mediaType = msg.message?.imageMessage ? 'photo'
|
|
192
|
+
: msg.message?.videoMessage ? 'video'
|
|
193
|
+
: msg.message?.audioMessage ? 'audio'
|
|
194
|
+
: msg.message?.documentMessage ? 'document'
|
|
195
|
+
: msg.message?.stickerMessage ? 'sticker' : 'file';
|
|
196
|
+
|
|
197
|
+
var ext = mediaType === 'photo' ? 'jpg' : mediaType === 'video' ? 'mp4'
|
|
198
|
+
: mediaType === 'audio' ? 'ogg' : mediaType === 'sticker' ? 'webp'
|
|
199
|
+
: (mediaMessage.fileName?.split('.').pop() || 'bin');
|
|
200
|
+
var localName = mediaMessage.fileName || `${mediaType}-${Date.now()}.${ext}`;
|
|
201
|
+
mediaPath = join(mediaDir, localName);
|
|
202
|
+
writeFileSync(mediaPath, buffer);
|
|
203
|
+
console.log(`[wa:${config.agentId.slice(0,8)}] Media downloaded: ${mediaPath} (${buffer.length} bytes)`);
|
|
204
|
+
}
|
|
205
|
+
} catch (dlErr: any) {
|
|
206
|
+
console.error(`[wa:${config.agentId.slice(0,8)}] Media download failed: ${dlErr.message}`);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Build text with media info
|
|
211
|
+
var finalText = text || '';
|
|
212
|
+
if (mediaPath) {
|
|
213
|
+
var desc = `[${mediaType}${mediaMessage?.fileName ? ': ' + mediaMessage.fileName : ''}] saved to: ${mediaPath}`;
|
|
214
|
+
finalText = text ? `${text}\n\n${desc}` : desc;
|
|
215
|
+
} else if (hasMedia && !text) {
|
|
216
|
+
finalText = `[${mediaType || 'media'} received but download failed]`;
|
|
217
|
+
}
|
|
218
|
+
|
|
166
219
|
conn.lastMessageAt = Date.now();
|
|
167
220
|
conn.events.emit('message', {
|
|
168
221
|
from: remoteJid,
|
|
169
222
|
senderJid,
|
|
170
223
|
pushName: msg.pushName,
|
|
171
|
-
text,
|
|
224
|
+
text: finalText,
|
|
172
225
|
timestamp: msg.messageTimestamp,
|
|
173
|
-
hasMedia
|
|
226
|
+
hasMedia,
|
|
227
|
+
mediaPath,
|
|
228
|
+
mediaType,
|
|
174
229
|
messageId: msg.key.id,
|
|
175
230
|
isGroup,
|
|
176
231
|
isSelfChat: !!isSelfChat,
|
package/src/auth/routes.ts
CHANGED
|
@@ -697,7 +697,7 @@ export function createAuthRoutes(
|
|
|
697
697
|
if (!target) return c.json({ error: 'User not found' }, 404);
|
|
698
698
|
|
|
699
699
|
// Generate a short-lived token (1 hour) for the target user with impersonation flag
|
|
700
|
-
const impersonateToken = await new SignJWT({ sub: target.id, role: target.role, impersonatedBy: caller.id })
|
|
700
|
+
const impersonateToken = await new SignJWT({ sub: target.id, role: target.role, impersonatedBy: caller.id, clientOrgId: target.clientOrgId || undefined, orgId: target.clientOrgId || (target as any).org_id || undefined })
|
|
701
701
|
.setProtectedHeader({ alg: 'HS256' })
|
|
702
702
|
.setIssuedAt()
|
|
703
703
|
.setExpirationTime('1h')
|
package/src/cli-agent.ts
CHANGED
|
@@ -393,11 +393,8 @@ export async function runAgent(_args: string[]) {
|
|
|
393
393
|
console.log(' Connecting to database...');
|
|
394
394
|
|
|
395
395
|
// 1. Connect to shared enterprise DB
|
|
396
|
-
const { createAdapter } = await import('./db/factory.js');
|
|
397
|
-
const db = await createAdapter(
|
|
398
|
-
type: DATABASE_URL.startsWith('postgres') ? 'postgres' : 'sqlite',
|
|
399
|
-
connectionString: DATABASE_URL,
|
|
400
|
-
});
|
|
396
|
+
const { createAdapter, smartDbConfig } = await import('./db/factory.js');
|
|
397
|
+
const db = await createAdapter(smartDbConfig(DATABASE_URL));
|
|
401
398
|
await db.migrate();
|
|
402
399
|
|
|
403
400
|
// 2. Initialize engine DB
|
|
@@ -902,6 +899,7 @@ export async function runAgent(_args: string[]) {
|
|
|
902
899
|
isDM: boolean;
|
|
903
900
|
messageText: string;
|
|
904
901
|
isManager?: boolean;
|
|
902
|
+
mediaFiles?: Array<{ path: string; type: string; mimeType?: string }>;
|
|
905
903
|
}>();
|
|
906
904
|
|
|
907
905
|
const isMessagingSource = ['whatsapp', 'telegram'].includes(ctx.source);
|
|
@@ -1063,6 +1061,23 @@ export async function runAgent(_args: string[]) {
|
|
|
1063
1061
|
`- No markdown formatting — plain text only.`,
|
|
1064
1062
|
`- For simple greetings/questions, reply in ONE tool call. Do not overthink.`,
|
|
1065
1063
|
'',
|
|
1064
|
+
`DEPENDENCY & TOOL MANAGEMENT:`,
|
|
1065
|
+
`- Before running commands that need specific tools (ffmpeg, imagemagick, etc.), use check_dependency to verify they're installed.`,
|
|
1066
|
+
`- If a tool is missing: use install_dependency to install it automatically (brew on macOS, apt on Linux).`,
|
|
1067
|
+
`- Tell your manager what you're installing and why — don't just silently install things. Example: "I need to install ffmpeg to process your video. Installing now..."`,
|
|
1068
|
+
`- After completing a task that required installing new tools, use cleanup_installed to review what was installed.`,
|
|
1069
|
+
`- For common tools (ffmpeg, imagemagick, jq, etc.) you can install without explicit permission — just inform.`,
|
|
1070
|
+
`- For unusual packages or large installs, ask your manager first.`,
|
|
1071
|
+
`- If installation fails, explain what happened and suggest alternatives.`,
|
|
1072
|
+
`- Use check_environment at the start of complex tasks to understand what's available.`,
|
|
1073
|
+
'',
|
|
1074
|
+
`FILE & MEDIA HANDLING:`,
|
|
1075
|
+
`- When you receive media files (images, videos, documents), they are saved locally and you can access them.`,
|
|
1076
|
+
`- For images: you can see them directly in the message. Describe what you see.`,
|
|
1077
|
+
`- For videos/audio: use ffmpeg (check_dependency first) to analyze, convert, or edit.`,
|
|
1078
|
+
`- For documents: use the appropriate tool to read/process them.`,
|
|
1079
|
+
`- You can send media back using ${ctx.source === 'telegram' ? 'telegram_send_media' : 'whatsapp_send_media'} with a local file path.`,
|
|
1080
|
+
'',
|
|
1066
1081
|
buildScheduleInfo(agentSchedule, agentTimezone),
|
|
1067
1082
|
ambientContext ? `\nCONTEXT FROM MEMORY:\n${ambientContext}` : '',
|
|
1068
1083
|
].filter(Boolean).join('\n');
|
|
@@ -1118,11 +1133,37 @@ export async function runAgent(_args: string[]) {
|
|
|
1118
1133
|
});
|
|
1119
1134
|
} catch (e: any) { /* non-fatal */ }
|
|
1120
1135
|
|
|
1136
|
+
// Build multimodal message content if media files are present
|
|
1137
|
+
let chatMessageContent: string = ctx.messageText;
|
|
1138
|
+
let mediaContentBlocks: any[] | undefined;
|
|
1139
|
+
if ((ctx as any).mediaFiles && (ctx as any).mediaFiles.length > 0) {
|
|
1140
|
+
const { readFileSync } = await import('fs');
|
|
1141
|
+
const blocks: any[] = [];
|
|
1142
|
+
if (ctx.messageText) blocks.push({ type: 'text', text: ctx.messageText });
|
|
1143
|
+
for (const media of (ctx as any).mediaFiles) {
|
|
1144
|
+
try {
|
|
1145
|
+
const buf = readFileSync(media.path);
|
|
1146
|
+
const b64 = buf.toString('base64');
|
|
1147
|
+
const mime = media.mimeType || (media.type === 'photo' ? 'image/jpeg' : 'application/octet-stream');
|
|
1148
|
+
if (mime.startsWith('image/')) {
|
|
1149
|
+
blocks.push({ type: 'image', source: { type: 'base64', media_type: mime, data: b64 } });
|
|
1150
|
+
blocks.push({ type: 'text', text: `[Image saved at: ${media.path}]` });
|
|
1151
|
+
} else {
|
|
1152
|
+
blocks.push({ type: 'text', text: `[File received: ${media.path} (${mime}). Use tools to read/process this file.]` });
|
|
1153
|
+
}
|
|
1154
|
+
} catch (fileErr: any) {
|
|
1155
|
+
blocks.push({ type: 'text', text: `[Media file: ${media.path} — could not read: ${fileErr.message}]` });
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
if (blocks.length > 0) mediaContentBlocks = blocks;
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1121
1161
|
const session = await runtime.spawnSession({
|
|
1122
1162
|
agentId: agentId,
|
|
1123
|
-
message:
|
|
1163
|
+
message: chatMessageContent,
|
|
1124
1164
|
systemPrompt,
|
|
1125
1165
|
...(sessionContext ? { sessionContext } : {}),
|
|
1166
|
+
...(mediaContentBlocks ? { messageContent: mediaContentBlocks } : {}),
|
|
1126
1167
|
});
|
|
1127
1168
|
|
|
1128
1169
|
// Mark task as in progress
|
package/src/cli-serve.ts
CHANGED
|
@@ -121,13 +121,10 @@ export async function runServe(_args: string[]) {
|
|
|
121
121
|
process.exit(1);
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
-
const { createAdapter } = await import('./db/factory.js');
|
|
124
|
+
const { createAdapter, smartDbConfig } = await import('./db/factory.js');
|
|
125
125
|
const { createServer } = await import('./server.js');
|
|
126
126
|
|
|
127
|
-
const db = await createAdapter(
|
|
128
|
-
type: DATABASE_URL.startsWith('postgres') ? 'postgres' : 'sqlite',
|
|
129
|
-
connectionString: DATABASE_URL,
|
|
130
|
-
});
|
|
127
|
+
const db = await createAdapter(smartDbConfig(DATABASE_URL));
|
|
131
128
|
|
|
132
129
|
await db.migrate();
|
|
133
130
|
|
package/src/dashboard/app.js
CHANGED
|
@@ -237,10 +237,15 @@ function App() {
|
|
|
237
237
|
if (d.user.permissions) setPermissions(d.user.permissions);
|
|
238
238
|
if (d.user.clientOrgId) {
|
|
239
239
|
localStorage.setItem('em_client_org_id', d.user.clientOrgId);
|
|
240
|
+
// Auto-select the client org so all pages filter by it
|
|
241
|
+
onOrgChange(d.user.clientOrgId, null);
|
|
240
242
|
apiCall('/organizations/' + d.user.clientOrgId).then(function(o) {
|
|
241
|
-
if (o && o.name)
|
|
243
|
+
if (o && o.name) {
|
|
244
|
+
setImpersonating(function(prev) { return prev ? Object.assign({}, prev, { user: Object.assign({}, prev.user, { clientOrgName: o.name }) }) : prev; });
|
|
245
|
+
onOrgChange(d.user.clientOrgId, o);
|
|
246
|
+
}
|
|
242
247
|
}).catch(function() {});
|
|
243
|
-
} else localStorage.removeItem('em_client_org_id');
|
|
248
|
+
} else { localStorage.removeItem('em_client_org_id'); onOrgChange('', null); }
|
|
244
249
|
toast('Now viewing as ' + d.user.name, 'info');
|
|
245
250
|
setPage('dashboard');
|
|
246
251
|
}
|
|
@@ -255,6 +260,7 @@ function App() {
|
|
|
255
260
|
return null;
|
|
256
261
|
});
|
|
257
262
|
localStorage.removeItem('em_client_org_id');
|
|
263
|
+
onOrgChange('', null); // Reset org selection back to platform org
|
|
258
264
|
authCall('/me').then(d => { setUser(d.user || d); }).catch(() => {});
|
|
259
265
|
apiCall('/me/permissions').then(d => { if (d && d.permissions) setPermissions(d.permissions); }).catch(() => {});
|
|
260
266
|
toast('Stopped impersonation', 'success');
|
|
@@ -136,7 +136,11 @@ export function OrgContextSwitcher(props) {
|
|
|
136
136
|
*/
|
|
137
137
|
export function useOrgContext() {
|
|
138
138
|
var app = useApp();
|
|
139
|
-
var
|
|
139
|
+
var user = app.user || {};
|
|
140
|
+
var userClientOrgId = user.clientOrgId || null;
|
|
141
|
+
var isLocked = !!userClientOrgId && user.role !== 'owner' && user.role !== 'admin';
|
|
142
|
+
// If user is org-bound (locked), always use their clientOrgId regardless of selectedOrgId
|
|
143
|
+
var selectedOrgId = isLocked ? userClientOrgId : (app.selectedOrgId || '');
|
|
140
144
|
var selectedOrg = app.selectedOrg || null;
|
|
141
145
|
var onOrgChange = app.onOrgChange || function() {};
|
|
142
146
|
|